use std::fmt;
use num_bigint::BigInt;
use num_integer::Integer;
use num_rational::Ratio;
use num_traits::{One, Zero};
pub trait Ring: Clone + PartialEq + fmt::Debug + Sized {
fn zero() -> Self;
fn one() -> Self;
fn is_zero(&self) -> bool;
fn is_one(&self) -> bool {
*self == Self::one()
}
fn add(&self, rhs: &Self) -> Self;
fn sub(&self, rhs: &Self) -> Self;
fn mul(&self, rhs: &Self) -> Self;
fn neg(&self) -> Self;
fn pow_usize(&self, mut n: usize) -> Self {
if n == 0 {
return Self::one();
}
let mut base = self.clone();
let mut result = Self::one();
while n > 1 {
if n & 1 == 1 {
result = result.mul(&base);
}
base = base.mul(&base);
n >>= 1;
}
result.mul(&base)
}
}
pub trait EuclideanDomain: Ring {
fn div_rem(&self, other: &Self) -> (Self, Self);
fn gcd(a: &Self, b: &Self) -> Self {
let mut a = a.clone();
let mut b = b.clone();
while !b.is_zero() {
let (_, r) = a.div_rem(&b);
a = b;
b = r;
}
a
}
}
pub trait Field: EuclideanDomain {
fn div(&self, other: &Self) -> Self;
fn inv(&self) -> Self;
fn poly_gcd(a: &[Self], b: &[Self]) -> Option<Vec<Self>> {
let _ = (a, b);
None
}
fn poly_extended_gcd(a: &[Self], b: &[Self]) -> Option<num_integer::ExtendedGcd<Vec<Self>>> {
let _ = (a, b);
None
}
}
pub trait IntegralCoeff: Ring {
fn is_integer(&self) -> bool;
fn to_integer(&self) -> Option<BigInt>;
fn from_integer(n: BigInt) -> Self;
fn from_i64(n: i64) -> Self {
Self::from_integer(BigInt::from(n))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum BindingStrength {
Weakest,
Sum,
Product,
Power,
Atom,
}
pub trait CoeffDisplay {
fn fmt_coeff(&self, f: &mut fmt::Formatter<'_>, env: BindingStrength) -> fmt::Result;
}
impl Ring for Ratio<BigInt> {
#[inline]
fn zero() -> Self {
<Ratio<BigInt> as Zero>::zero()
}
#[inline]
fn one() -> Self {
<Ratio<BigInt> as One>::one()
}
#[inline]
fn is_zero(&self) -> bool {
Zero::is_zero(self)
}
#[inline]
fn is_one(&self) -> bool {
One::is_one(self)
}
#[inline]
fn add(&self, rhs: &Self) -> Self {
self + rhs
}
#[inline]
fn sub(&self, rhs: &Self) -> Self {
self - rhs
}
#[inline]
fn mul(&self, rhs: &Self) -> Self {
self * rhs
}
#[inline]
fn neg(&self) -> Self {
-self
}
}
impl EuclideanDomain for Ratio<BigInt> {
#[inline]
fn div_rem(&self, other: &Self) -> (Self, Self) {
(self / other, <Self as Ring>::zero())
}
#[inline]
fn gcd(a: &Self, b: &Self) -> Self {
if Ring::is_zero(a) && Ring::is_zero(b) {
<Self as Ring>::zero()
} else {
<Self as Ring>::one()
}
}
}
impl Field for Ratio<BigInt> {
#[inline]
fn div(&self, other: &Self) -> Self {
self / other
}
#[inline]
fn inv(&self) -> Self {
Ratio::new(self.denom().clone(), self.numer().clone())
}
fn poly_gcd(a: &[Self], b: &[Self]) -> Option<Vec<Self>> {
Some(super::zpoly::gcd_via_z(a, b))
}
fn poly_extended_gcd(a: &[Self], b: &[Self]) -> Option<num_integer::ExtendedGcd<Vec<Self>>> {
super::zpoly::extended_gcd_via_z(a, b)
}
}
impl IntegralCoeff for Ratio<BigInt> {
#[inline]
fn is_integer(&self) -> bool {
One::is_one(self.denom())
}
fn to_integer(&self) -> Option<BigInt> {
if self.is_integer() {
Some(self.numer().clone())
} else {
None
}
}
#[inline]
fn from_integer(n: BigInt) -> Self {
Ratio::from_integer(n)
}
}
impl CoeffDisplay for Ratio<BigInt> {
fn fmt_coeff(&self, f: &mut fmt::Formatter<'_>, env: BindingStrength) -> fmt::Result {
if One::is_one(self.denom()) {
let n = self.numer();
if n.sign() == num_bigint::Sign::Minus && env >= BindingStrength::Sum {
write!(f, "({})", n)
} else {
write!(f, "{}", n)
}
} else {
if env >= BindingStrength::Product {
write!(f, "({}/{})", self.numer(), self.denom())
} else {
write!(f, "{}/{}", self.numer(), self.denom())
}
}
}
}
impl Ring for BigInt {
#[inline]
fn zero() -> Self {
<BigInt as Zero>::zero()
}
#[inline]
fn one() -> Self {
<BigInt as One>::one()
}
#[inline]
fn is_zero(&self) -> bool {
Zero::is_zero(self)
}
#[inline]
fn is_one(&self) -> bool {
One::is_one(self)
}
#[inline]
fn add(&self, rhs: &Self) -> Self {
self + rhs
}
#[inline]
fn sub(&self, rhs: &Self) -> Self {
self - rhs
}
#[inline]
fn mul(&self, rhs: &Self) -> Self {
self * rhs
}
#[inline]
fn neg(&self) -> Self {
-self
}
}
impl EuclideanDomain for BigInt {
#[inline]
fn div_rem(&self, other: &Self) -> (Self, Self) {
Integer::div_rem(self, other)
}
#[inline]
fn gcd(a: &Self, b: &Self) -> Self {
Integer::gcd(a, b)
}
}
impl IntegralCoeff for BigInt {
#[inline]
fn is_integer(&self) -> bool {
true
}
#[inline]
fn to_integer(&self) -> Option<BigInt> {
Some(self.clone())
}
#[inline]
fn from_integer(n: BigInt) -> Self {
n
}
}
impl CoeffDisplay for BigInt {
fn fmt_coeff(&self, f: &mut fmt::Formatter<'_>, env: BindingStrength) -> fmt::Result {
if self.sign() == num_bigint::Sign::Minus && env >= BindingStrength::Sum {
write!(f, "({})", self)
} else {
write!(f, "{}", self)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type Q = Ratio<BigInt>;
fn q(n: i64, d: i64) -> Q {
Ratio::new(BigInt::from(n), BigInt::from(d))
}
fn qzero() -> Q {
<Q as Ring>::zero()
}
fn qone() -> Q {
<Q as Ring>::one()
}
#[test]
fn ring_additive_identity() {
let a = q(3, 4);
assert_eq!(Ring::add(&a, &qzero()), a);
assert_eq!(Ring::add(&qzero(), &a), a);
}
#[test]
fn ring_multiplicative_identity() {
let a = q(3, 4);
assert_eq!(Ring::mul(&a, &qone()), a);
assert_eq!(Ring::mul(&qone(), &a), a);
}
#[test]
fn ring_additive_inverse() {
let a = q(3, 4);
let neg_a = Ring::neg(&a);
assert_eq!(Ring::add(&a, &neg_a), qzero());
}
#[test]
fn ring_commutativity() {
let a = q(2, 3);
let b = q(5, 7);
assert_eq!(Ring::add(&a, &b), Ring::add(&b, &a));
assert_eq!(Ring::mul(&a, &b), Ring::mul(&b, &a));
}
#[test]
fn ring_associativity() {
let a = q(1, 2);
let b = q(1, 3);
let c = q(1, 5);
assert_eq!(
Ring::add(&Ring::add(&a, &b), &c),
Ring::add(&a, &Ring::add(&b, &c))
);
assert_eq!(
Ring::mul(&Ring::mul(&a, &b), &c),
Ring::mul(&a, &Ring::mul(&b, &c))
);
}
#[test]
fn ring_distributivity() {
let a = q(2, 3);
let b = q(1, 4);
let c = q(5, 6);
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);
}
#[test]
fn ring_is_zero_and_is_one() {
assert!(Ring::is_zero(&qzero()));
assert!(!Ring::is_zero(&qone()));
assert!(Ring::is_one(&qone()));
assert!(!Ring::is_one(&qzero()));
assert!(!Ring::is_zero(&q(2, 3)));
assert!(!Ring::is_one(&q(2, 3)));
}
#[test]
fn ring_pow_usize() {
let a = q(2, 3);
assert_eq!(a.pow_usize(0), qone());
assert_eq!(a.pow_usize(1), q(2, 3));
assert_eq!(a.pow_usize(2), q(4, 9));
assert_eq!(a.pow_usize(3), q(8, 27));
}
#[test]
fn field_division() {
let a = q(3, 4);
let b = q(2, 5);
assert_eq!(Field::div(&a, &b), q(15, 8));
}
#[test]
fn field_inverse() {
let a = q(3, 7);
let inv = Field::inv(&a);
assert_eq!(Ring::mul(&a, &inv), qone());
}
#[test]
fn field_div_rem_trivial() {
let a = q(5, 3);
let b = q(2, 7);
let (quot, rem) = EuclideanDomain::div_rem(&a, &b);
assert_eq!(quot, Field::div(&a, &b));
assert!(Ring::is_zero(&rem));
}
#[test]
fn field_gcd_is_one() {
let a = q(3, 4);
let b = q(5, 6);
assert_eq!(EuclideanDomain::gcd(&a, &b), qone());
}
#[test]
fn field_gcd_with_zero() {
let a = qzero();
let b = q(5, 6);
assert_eq!(EuclideanDomain::gcd(&a, &b), qone());
assert!(Ring::is_zero(&EuclideanDomain::gcd(&a, &a)));
}
#[test]
fn integral_is_integer() {
assert!(IntegralCoeff::is_integer(&q(5, 1)));
assert!(!IntegralCoeff::is_integer(&q(5, 3)));
assert!(IntegralCoeff::is_integer(&qzero()));
}
#[test]
fn integral_to_integer() {
assert_eq!(IntegralCoeff::to_integer(&q(7, 1)), Some(BigInt::from(7)));
assert_eq!(IntegralCoeff::to_integer(&q(7, 3)), None);
}
#[test]
fn integral_from_integer() {
let x = <Q as IntegralCoeff>::from_integer(BigInt::from(42));
assert_eq!(x, q(42, 1));
}
#[test]
fn integral_from_i64() {
let x = <Q as IntegralCoeff>::from_i64(-5);
assert_eq!(x, q(-5, 1));
}
fn format_coeff<C: CoeffDisplay>(c: &C, env: BindingStrength) -> String {
use std::fmt::Write;
let mut s = String::new();
struct Wrapper<'a, C>(&'a C, BindingStrength);
impl<C: CoeffDisplay> fmt::Display for Wrapper<'_, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_coeff(f, self.1)
}
}
write!(s, "{}", Wrapper(c, env)).unwrap();
s
}
#[test]
fn display_integer_in_sum() {
assert_eq!(format_coeff(&q(5, 1), BindingStrength::Sum), "5");
}
#[test]
fn display_integer_in_product() {
assert_eq!(format_coeff(&q(5, 1), BindingStrength::Product), "5");
}
#[test]
fn display_negative_integer_in_sum() {
assert_eq!(format_coeff(&q(-5, 1), BindingStrength::Sum), "(-5)");
}
#[test]
fn display_negative_integer_at_top_level() {
assert_eq!(format_coeff(&q(-5, 1), BindingStrength::Weakest), "-5");
}
#[test]
fn display_fraction_in_sum() {
assert_eq!(format_coeff(&q(3, 4), BindingStrength::Sum), "3/4");
}
#[test]
fn display_fraction_in_product() {
assert_eq!(format_coeff(&q(3, 4), BindingStrength::Product), "(3/4)");
}
#[test]
fn display_one_in_product() {
assert_eq!(format_coeff(&q(1, 1), BindingStrength::Product), "1");
}
#[test]
fn display_zero() {
assert_eq!(format_coeff(&qzero(), BindingStrength::Weakest), "0");
}
#[test]
fn binding_strength_ordering() {
assert!(BindingStrength::Weakest < BindingStrength::Sum);
assert!(BindingStrength::Sum < BindingStrength::Product);
assert!(BindingStrength::Product < BindingStrength::Power);
assert!(BindingStrength::Power < BindingStrength::Atom);
}
fn z(n: i64) -> BigInt {
BigInt::from(n)
}
#[test]
fn bigint_ring_ops() {
assert_eq!(<BigInt as Ring>::zero(), z(0));
assert_eq!(<BigInt as Ring>::one(), z(1));
assert!(Ring::is_zero(&z(0)) && !Ring::is_zero(&z(3)));
assert!(Ring::is_one(&z(1)) && !Ring::is_one(&z(-1)));
assert_eq!(Ring::add(&z(7), &z(-9)), z(-2));
assert_eq!(Ring::sub(&z(7), &z(-9)), z(16));
assert_eq!(Ring::mul(&z(7), &z(-9)), z(-63));
assert_eq!(Ring::neg(&z(7)), z(-7));
assert_eq!(z(-3).pow_usize(3), z(-27));
}
#[test]
fn bigint_div_rem_is_truncated() {
assert_eq!(EuclideanDomain::div_rem(&z(7), &z(2)), (z(3), z(1)));
assert_eq!(EuclideanDomain::div_rem(&z(-7), &z(2)), (z(-3), z(-1)));
assert_eq!(EuclideanDomain::div_rem(&z(7), &z(-2)), (z(-3), z(1)));
assert_eq!(EuclideanDomain::div_rem(&z(-6), &z(3)), (z(-2), z(0)));
}
#[test]
fn bigint_gcd_is_non_negative() {
assert_eq!(<BigInt as EuclideanDomain>::gcd(&z(-12), &z(18)), z(6));
assert_eq!(<BigInt as EuclideanDomain>::gcd(&z(-4), &z(0)), z(4));
assert_eq!(<BigInt as EuclideanDomain>::gcd(&z(0), &z(0)), z(0));
}
#[test]
fn bigint_integral_coeff() {
assert!(IntegralCoeff::is_integer(&z(5)));
assert_eq!(IntegralCoeff::to_integer(&z(5)), Some(z(5)));
assert_eq!(<BigInt as IntegralCoeff>::from_integer(z(9)), z(9));
assert_eq!(<BigInt as IntegralCoeff>::from_i64(-4), z(-4));
}
#[test]
fn bigint_coeff_display() {
assert_eq!(format_coeff(&z(3), BindingStrength::Product), "3");
assert_eq!(format_coeff(&z(-3), BindingStrength::Weakest), "-3");
assert_eq!(format_coeff(&z(-3), BindingStrength::Sum), "(-3)");
assert_eq!(format_coeff(&z(-3), BindingStrength::Product), "(-3)");
}
}