use std::fmt;
use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
use super::dense::Poly;
use super::traits::{BindingStrength, CoeffDisplay, EuclideanDomain, Field, IntegralCoeff, Ring};
#[derive(Clone)]
pub struct RationalFn {
numer: Poly,
denom: Poly,
}
impl RationalFn {
pub fn new(numer: Poly, denom: Poly) -> Self {
assert!(!denom.is_zero(), "RationalFn: denominator must be nonzero");
let mut rf = RationalFn { numer, denom };
rf.reduce();
rf
}
pub fn from_poly(p: Poly) -> Self {
RationalFn {
numer: p,
denom: Poly::from_int(1),
}
}
pub fn from_int(n: i64) -> Self {
Self::from_poly(Poly::from_int(n))
}
pub fn from_rational(r: Ratio<BigInt>) -> Self {
Self::from_poly(Poly::constant(r))
}
#[allow(dead_code)] pub fn numer(&self) -> &Poly {
&self.numer
}
#[allow(dead_code)] pub fn denom(&self) -> &Poly {
&self.denom
}
pub fn is_constant_rational(&self) -> bool {
self.numer.is_constant() && self.denom.is_constant()
}
pub fn to_rational(&self) -> Option<Ratio<BigInt>> {
if !self.is_constant_rational() {
return None;
}
let n = self.numer.coeff(0);
let d = self.denom.coeff(0);
if Zero::is_zero(&d) {
return None;
}
Some(n / d)
}
fn reduce(&mut self) {
if self.numer.is_zero() {
self.denom = Poly::from_int(1);
return;
}
let g = Poly::gcd(&self.numer, &self.denom);
if let Some(g_lc) = g.leading_coeff()
&& (g.degree() > Some(0) || !One::is_one(g_lc))
{
self.numer = self.numer.div_rem(&g).0;
self.denom = self.denom.div_rem(&g).0;
}
if let Some(lc) = self.denom.leading_coeff()
&& !One::is_one(lc)
{
let lc = lc.clone();
let inv_lc = Ratio::new(lc.denom().clone(), lc.numer().clone());
self.numer = self.numer.scale(&inv_lc);
self.denom = self.denom.scale(&inv_lc);
}
}
}
impl PartialEq for RationalFn {
fn eq(&self, other: &Self) -> bool {
self.numer == other.numer && self.denom == other.denom
}
}
impl Ring for RationalFn {
fn zero() -> Self {
RationalFn {
numer: Poly::zero(),
denom: Poly::from_int(1),
}
}
fn one() -> Self {
RationalFn {
numer: Poly::from_int(1),
denom: Poly::from_int(1),
}
}
fn is_zero(&self) -> bool {
self.numer.is_zero()
}
fn is_one(&self) -> bool {
!self.numer.is_zero()
&& self.numer.is_constant()
&& self.denom.is_constant()
&& self.numer.coeff(0) == self.denom.coeff(0)
}
fn add(&self, rhs: &Self) -> Self {
let numer = &(&self.numer * &rhs.denom) + &(&rhs.numer * &self.denom);
let denom = &self.denom * &rhs.denom;
RationalFn::new(numer, denom)
}
fn sub(&self, rhs: &Self) -> Self {
let numer = &(&self.numer * &rhs.denom) - &(&rhs.numer * &self.denom);
let denom = &self.denom * &rhs.denom;
RationalFn::new(numer, denom)
}
fn mul(&self, rhs: &Self) -> Self {
let numer = &self.numer * &rhs.numer;
let denom = &self.denom * &rhs.denom;
RationalFn::new(numer, denom)
}
fn neg(&self) -> Self {
RationalFn {
numer: -&self.numer,
denom: self.denom.clone(),
}
}
}
impl EuclideanDomain for RationalFn {
fn div_rem(&self, other: &Self) -> (Self, Self) {
(Field::div(self, other), Self::zero())
}
fn gcd(a: &Self, b: &Self) -> Self {
if Ring::is_zero(a) && Ring::is_zero(b) {
Self::zero()
} else {
Self::one()
}
}
}
impl Field for RationalFn {
fn div(&self, other: &Self) -> Self {
assert!(!other.is_zero(), "RationalFn: division by zero");
let numer = &self.numer * &other.denom;
let denom = &self.denom * &other.numer;
RationalFn::new(numer, denom)
}
fn inv(&self) -> Self {
assert!(!self.is_zero(), "RationalFn: inverse of zero");
RationalFn::new(self.denom.clone(), self.numer.clone())
}
}
impl IntegralCoeff for RationalFn {
fn is_integer(&self) -> bool {
if !self.is_constant_rational() {
return false;
}
let n = self.numer.coeff(0);
let d = self.denom.coeff(0);
if Zero::is_zero(&d) {
return false;
}
let val = n / d;
val.denom().is_one()
}
fn to_integer(&self) -> Option<BigInt> {
if !self.is_integer() {
return None;
}
let val = self.numer.coeff(0) / self.denom.coeff(0);
if val.denom().is_one() {
Some(val.numer().clone())
} else {
None
}
}
fn from_integer(n: BigInt) -> Self {
RationalFn::from_poly(Poly::constant(Ratio::from_integer(n)))
}
}
impl fmt::Display for RationalFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.denom.is_constant() && One::is_one(&self.denom.coeff(0)) {
write!(f, "{}", self.numer)
} else {
write!(f, "({})/({})", self.numer, self.denom)
}
}
}
impl fmt::Debug for RationalFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RationalFn({}/{})", self.numer, self.denom)
}
}
impl CoeffDisplay for RationalFn {
fn fmt_coeff(&self, f: &mut fmt::Formatter<'_>, env: BindingStrength) -> fmt::Result {
if self.denom.is_constant() && One::is_one(&self.denom.coeff(0)) {
if self.numer.is_constant() {
self.numer.coeff(0).fmt_coeff(f, env)
} else {
let needs_parens =
env >= BindingStrength::Product && self.numer.degree().unwrap_or(0) > 0;
if needs_parens {
write!(f, "({})", self.numer)
} else {
write!(f, "{}", self.numer)
}
}
} else {
if env >= BindingStrength::Product {
write!(f, "(({})/({}))", self.numer, self.denom)
} else {
write!(f, "({})/({})", self.numer, self.denom)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64, d: i64) -> Ratio<BigInt> {
Ratio::new(BigInt::from(n), BigInt::from(d))
}
fn rf_int(n: i64) -> RationalFn {
RationalFn::from_int(n)
}
fn rf(numer: &[i64], denom: &[i64]) -> RationalFn {
let n = Poly::from_coeffs(numer.iter().map(|&c| r(c, 1)).collect());
let d = Poly::from_coeffs(denom.iter().map(|&c| r(c, 1)).collect());
RationalFn::new(n, d)
}
fn rf_poly(cs: &[i64]) -> RationalFn {
RationalFn::from_poly(Poly::from_coeffs(cs.iter().map(|&c| r(c, 1)).collect()))
}
fn rfzero() -> RationalFn {
<RationalFn as Ring>::zero()
}
fn rfone() -> RationalFn {
<RationalFn as Ring>::one()
}
#[test]
fn from_int_basic() {
let a = rf_int(5);
assert!(a.is_constant_rational());
assert_eq!(a.to_rational(), Some(r(5, 1)));
}
#[test]
fn from_poly_basic() {
let a = rf_poly(&[1, 1]);
assert!(!a.is_constant_rational());
assert_eq!(a.denom(), &Poly::from_int(1));
}
#[test]
fn reduction_cancels_common_factor() {
let numer = Poly::from_coeffs(vec![r(-1, 1), r(0, 1), r(1, 1)]); let denom = Poly::from_coeffs(vec![r(-1, 1), r(1, 1)]); let a = RationalFn::new(numer, denom);
assert_eq!(a.denom(), &Poly::from_int(1));
assert_eq!(a.numer().degree(), Some(1));
assert_eq!(a.numer().coeff(0), r(1, 1));
assert_eq!(a.numer().coeff(1), r(1, 1));
}
#[test]
fn reduction_makes_denom_monic() {
let numer = Poly::from_int(1);
let denom = Poly::from_coeffs(vec![r(4, 1), r(2, 1)]); let a = RationalFn::new(numer, denom);
assert_eq!(a.denom().leading_coeff(), Some(&r(1, 1)));
}
#[test]
fn zero_numer_gives_zero() {
let a = RationalFn::new(Poly::zero(), Poly::from_int(7));
assert!(Ring::is_zero(&a));
assert_eq!(a.denom(), &Poly::from_int(1));
}
#[test]
fn ring_additive_identity() {
let a = rf(&[1, 1], &[1]); assert_eq!(Ring::add(&a, &rfzero()), a);
assert_eq!(Ring::add(&rfzero(), &a), a);
}
#[test]
fn ring_multiplicative_identity() {
let a = rf(&[1, 1], &[1]); assert_eq!(Ring::mul(&a, &rfone()), a);
assert_eq!(Ring::mul(&rfone(), &a), a);
}
#[test]
fn ring_additive_inverse() {
let a = rf(&[3, 2], &[1, 1]); let neg_a = Ring::neg(&a);
let sum = Ring::add(&a, &neg_a);
assert!(Ring::is_zero(&sum), "a + (-a) should be 0, got {sum}");
}
#[test]
fn ring_commutativity_add() {
let a = rf(&[1], &[1, 1]); let b = rf(&[1], &[-1, 1]); assert_eq!(Ring::add(&a, &b), Ring::add(&b, &a));
}
#[test]
fn ring_commutativity_mul() {
let a = rf(&[1], &[1, 1]); let b = rf(&[1, 1], &[1]); assert_eq!(Ring::mul(&a, &b), Ring::mul(&b, &a));
}
#[test]
fn ring_is_zero_and_is_one() {
assert!(Ring::is_zero(&rfzero()));
assert!(!Ring::is_zero(&rfone()));
assert!(Ring::is_one(&rfone()));
assert!(!Ring::is_one(&rfzero()));
}
#[test]
fn add_same_denom() {
let a = rf(&[1], &[1, 1]);
let b = rf(&[2], &[1, 1]);
let c = Ring::add(&a, &b);
assert_eq!(c, rf(&[3], &[1, 1]));
}
#[test]
fn add_different_denom() {
let a = rf(&[1], &[0, 1]); let b = rf(&[1], &[0, 0, 1]); let c = Ring::add(&a, &b);
assert_eq!(c.numer().degree(), Some(1));
assert_eq!(c.denom().degree(), Some(2));
}
#[test]
fn mul_inverse_gives_one() {
let a = rf_poly(&[1, 1]); let b = rf(&[1], &[1, 1]); let c = Ring::mul(&a, &b);
assert!(Ring::is_one(&c), "(x+1) * 1/(x+1) should be 1, got {c}");
}
#[test]
fn mul_fractions() {
let a = rf(&[1, 1], &[0, 1]); let b = rf(&[0, 1], &[-1, 1]); let c = Ring::mul(&a, &b);
assert_eq!(c, rf(&[1, 1], &[-1, 1])); }
#[test]
fn sub_to_zero() {
let a = rf(&[1, 2, 3], &[1, 1]);
let b = Ring::sub(&a, &a);
assert!(Ring::is_zero(&b));
}
#[test]
fn field_division() {
let a = rf(&[1], &[0, 1]); let b = rf(&[1], &[0, 0, 1]); let c = Field::div(&a, &b);
assert_eq!(c, rf_poly(&[0, 1])); }
#[test]
fn field_inverse() {
let a = rf(&[1, 1], &[0, 1]); let inv = Field::inv(&a);
let product = Ring::mul(&a, &inv);
assert!(
Ring::is_one(&product),
"a * inv(a) should be 1, got {product}"
);
}
#[test]
fn field_div_rem_trivial() {
let a = rf(&[1], &[0, 1]); let b = rf(&[1], &[1, 1]); let (quot, rem) = EuclideanDomain::div_rem(&a, &b);
assert_eq!(quot, Field::div(&a, &b));
assert!(Ring::is_zero(&rem));
}
#[test]
fn constant_rational_detection() {
assert!(rf_int(3).is_constant_rational());
assert!(rfzero().is_constant_rational());
assert!(!rf_poly(&[0, 1]).is_constant_rational()); assert!(!rf(&[1], &[0, 1]).is_constant_rational()); }
#[test]
fn to_rational_extraction() {
assert_eq!(rf_int(7).to_rational(), Some(r(7, 1)));
assert_eq!(rfzero().to_rational(), Some(r(0, 1)));
let three_fourths = RationalFn::from_rational(r(3, 4));
assert_eq!(three_fourths.to_rational(), Some(r(3, 4)));
assert_eq!(rf_poly(&[0, 1]).to_rational(), None);
}
#[test]
fn integral_is_integer() {
assert!(IntegralCoeff::is_integer(&rf_int(5)));
assert!(IntegralCoeff::is_integer(&rf_int(0)));
assert!(!IntegralCoeff::is_integer(&RationalFn::from_rational(r(
1, 2
))));
assert!(!IntegralCoeff::is_integer(&rf_poly(&[0, 1]))); }
#[test]
fn integral_from_integer() {
let a = <RationalFn as IntegralCoeff>::from_integer(BigInt::from(42));
assert_eq!(a, rf_int(42));
}
#[test]
fn integral_to_integer() {
assert_eq!(IntegralCoeff::to_integer(&rf_int(7)), Some(BigInt::from(7)));
assert_eq!(IntegralCoeff::to_integer(&rf_poly(&[0, 1])), None);
}
#[test]
fn display_integer() {
assert_eq!(format!("{}", rf_int(5)), "5");
}
#[test]
fn display_polynomial() {
let a = rf_poly(&[1, 1]); let s = format!("{a}");
assert!(s.contains("θ"), "should display as polynomial, got: {s}");
}
#[test]
fn display_fraction() {
let a = rf(&[1], &[1, 1]); let s = format!("{a}");
assert!(s.contains("/"), "should display as fraction, got: {s}");
}
fn format_rfc(c: &RationalFn, env: BindingStrength) -> String {
struct W<'a>(&'a RationalFn, BindingStrength);
impl fmt::Display for W<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_coeff(f, self.1)
}
}
format!("{}", W(c, env))
}
#[test]
fn coeff_display_integer_in_product() {
let a = rf_int(5);
assert_eq!(format_rfc(&a, BindingStrength::Product), "5");
}
#[test]
fn coeff_display_polynomial_in_product() {
let a = rf_poly(&[1, 1]);
let s = format_rfc(&a, BindingStrength::Product);
assert!(
s.starts_with('('),
"polynomial coeff in product should have parens: {s}"
);
}
#[test]
fn coeff_display_fraction_in_product() {
let a = rf(&[1], &[1, 1]);
let s = format_rfc(&a, BindingStrength::Product);
assert!(
s.starts_with('('),
"fraction coeff in product should have parens: {s}"
);
}
#[test]
fn coeff_display_fraction_at_top_level() {
let a = rf(&[1], &[1, 1]);
let s = format_rfc(&a, BindingStrength::Weakest);
assert!(s.contains("/"), "fraction should show division: {s}");
}
#[test]
fn pow_usize_basic() {
let a = rf(&[1], &[0, 1]); let b = a.pow_usize(3);
assert_eq!(b.numer().degree(), Some(0)); assert_eq!(b.denom().degree(), Some(3)); }
#[test]
fn pow_usize_zero() {
let a = rf(&[1, 1], &[0, 1]); let b = a.pow_usize(0);
assert!(Ring::is_one(&b));
}
#[test]
fn distributivity() {
let a = rf(&[1], &[1, 1]); let b = rf(&[0, 1], &[1]); let c = rf(&[1], &[-1, 1]); let lhs = Ring::mul(&a, &Ring::add(&b, &c));
let rhs = Ring::add(&Ring::mul(&a, &b), &Ring::mul(&a, &c));
assert_eq!(
lhs, rhs,
"distributivity failed:\n lhs = {lhs}\n rhs = {rhs}"
);
}
}