use rug::Rational;
use super::number_field::{CoeffField, GPoly, Quotient};
use super::poly_rde::{degree, poly_deriv, poly_mul, poly_one, poly_scale, poly_zero, trim, QPoly};
use super::rational_rde::{poly_div_exact, poly_gcd, poly_sub};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RatFn {
num: QPoly,
den: QPoly,
}
impl RatFn {
pub fn new(num: QPoly, den: QPoly) -> Self {
let num = trim(num);
let den = trim(den);
assert!(!den.is_empty(), "RatFn: zero denominator");
if num.is_empty() {
return Self {
num: Vec::new(),
den: poly_one(),
};
}
let g = poly_gcd(&num, &den);
let mut num = poly_div_exact(&num, &g);
let mut den = poly_div_exact(&den, &g);
let lc = den[degree(&den) as usize].clone();
if lc != 1 {
let inv = Rational::from(1) / lc;
num = poly_scale(&num, &inv);
den = poly_scale(&den, &inv);
}
Self { num, den }
}
pub fn from_poly(p: &QPoly) -> Self {
Self::new(p.clone(), poly_one())
}
pub fn int(n: i64) -> Self {
Self::new(vec![Rational::from(n)], poly_one())
}
pub fn numer(&self) -> &QPoly {
&self.num
}
pub fn denom(&self) -> &QPoly {
&self.den
}
fn is_zero(&self) -> bool {
self.num.is_empty()
}
fn add(&self, other: &Self) -> Self {
let num = super::poly_rde::poly_add(
&poly_mul(&self.num, &other.den),
&poly_mul(&other.num, &self.den),
);
let den = poly_mul(&self.den, &other.den);
Self::new(num, den)
}
fn mul(&self, other: &Self) -> Self {
Self::new(
poly_mul(&self.num, &other.num),
poly_mul(&self.den, &other.den),
)
}
fn neg(&self) -> Self {
Self::new(poly_scale(&self.num, &Rational::from(-1)), self.den.clone())
}
fn inv(&self) -> Option<Self> {
if self.is_zero() {
None
} else {
Some(Self::new(self.den.clone(), self.num.clone()))
}
}
fn derivative(&self) -> Self {
let np = poly_deriv(&self.num);
let dp = poly_deriv(&self.den);
let numer = poly_sub(&poly_mul(&np, &self.den), &poly_mul(&self.num, &dp));
let denom = poly_mul(&self.den, &self.den);
Self::new(numer, denom)
}
}
#[derive(Clone, Debug, Default)]
pub struct RationalFunctionField;
impl CoeffField for RationalFunctionField {
type Elem = RatFn;
fn zero(&self) -> RatFn {
RatFn::int(0)
}
fn one(&self) -> RatFn {
RatFn::int(1)
}
fn from_i64(&self, n: i64) -> RatFn {
RatFn::int(n)
}
fn add(&self, a: &RatFn, b: &RatFn) -> RatFn {
a.add(b)
}
fn sub(&self, a: &RatFn, b: &RatFn) -> RatFn {
a.add(&b.neg())
}
fn mul(&self, a: &RatFn, b: &RatFn) -> RatFn {
a.mul(b)
}
fn neg(&self, a: &RatFn) -> RatFn {
a.neg()
}
fn inv(&self, a: &RatFn) -> Option<RatFn> {
a.inv()
}
fn is_zero(&self, a: &RatFn) -> bool {
a.is_zero()
}
fn eq(&self, a: &RatFn, b: &RatFn) -> bool {
a == b
}
fn derivation(&self, a: &RatFn) -> RatFn {
a.derivative()
}
}
pub type AlgElem = GPoly<RationalFunctionField>;
#[derive(Clone, Debug)]
pub struct AlgExtension {
quotient: Quotient<RationalFunctionField>,
dy: AlgElem,
}
impl AlgExtension {
pub fn new(q: &[QPoly]) -> Self {
let modulus: AlgElem = q.iter().map(RatFn::from_poly).collect();
let quotient = Quotient::new(RationalFunctionField, modulus);
let dy = radical_dy("ient);
Self { quotient, dy }
}
pub fn radical(n: usize, p: &QPoly) -> Self {
debug_assert!(n >= 2, "radical extension needs degree ≥ 2");
let mut q: Vec<QPoly> = vec![poly_zero(); n + 1];
q[0] = poly_scale(p, &Rational::from(-1)); q[n] = poly_one(); Self::new(&q)
}
pub fn quotient(&self) -> &Quotient<RationalFunctionField> {
&self.quotient
}
pub fn degree(&self) -> i64 {
self.quotient.degree()
}
pub fn dy(&self) -> &AlgElem {
&self.dy
}
pub fn generator(&self) -> AlgElem {
vec![RatFn::int(0), RatFn::int(1)]
}
pub fn constant(&self, r: RatFn) -> AlgElem {
self.quotient.reduce(&[r])
}
pub fn from_int(&self, n: i64) -> AlgElem {
self.quotient.from_int(n)
}
pub fn reduce(&self, a: &[RatFn]) -> AlgElem {
self.quotient.reduce(a)
}
pub fn add(&self, a: &[RatFn], b: &[RatFn]) -> AlgElem {
self.quotient.add(a, b)
}
pub fn sub(&self, a: &[RatFn], b: &[RatFn]) -> AlgElem {
self.quotient.sub(a, b)
}
pub fn mul(&self, a: &[RatFn], b: &[RatFn]) -> AlgElem {
self.quotient.mul(a, b)
}
pub fn neg(&self, a: &[RatFn]) -> AlgElem {
self.quotient.neg(a)
}
pub fn inv(&self, a: &[RatFn]) -> Option<AlgElem> {
self.quotient.inv(a)
}
pub fn pow(&self, a: &[RatFn], n: i64) -> Option<AlgElem> {
if n == 0 {
return Some(self.from_int(1));
}
if n < 0 {
let inv = self.inv(a)?;
return self.pow(&inv, -n);
}
let mut acc = self.from_int(1);
for _ in 0..n {
acc = self.mul(&acc, a);
}
Some(acc)
}
pub fn elem_eq(&self, a: &[RatFn], b: &[RatFn]) -> bool {
self.quotient.elem_eq(a, b)
}
pub fn is_zero(&self, a: &[RatFn]) -> bool {
self.quotient.elem_is_zero(a)
}
pub fn derivation(&self, a: &[RatFn]) -> AlgElem {
quotient_derivation(&self.quotient, &self.dy, a)
}
}
pub(crate) fn dpoly_dy<F: CoeffField>(field: &F, p: &[F::Elem]) -> GPoly<F> {
if p.len() <= 1 {
return Vec::new();
}
p[1..]
.iter()
.enumerate()
.map(|(i, c)| field.mul(&field.from_i64(i as i64 + 1), c))
.collect()
}
pub fn radical_dy<F: CoeffField>(quotient: &Quotient<F>) -> GPoly<F> {
let field = quotient.field();
let modulus = quotient.modulus();
let qx: GPoly<F> = modulus.iter().map(|c| field.derivation(c)).collect();
let qy = dpoly_dy(field, modulus);
let qx_red = quotient.reduce(&qx);
let qy_red = quotient.reduce(&qy);
let qy_inv = quotient
.inv(&qy_red)
.expect("q is separable (squarefree, char 0): q_y is invertible mod q");
quotient.mul("ient.neg(&qx_red), &qy_inv)
}
pub fn quotient_derivation<F: CoeffField>(
quotient: &Quotient<F>,
dy: &[F::Elem],
a: &[F::Elem],
) -> GPoly<F> {
let field = quotient.field();
let coeff_part: GPoly<F> = a.iter().map(|c| field.derivation(c)).collect();
let da_dy = dpoly_dy(field, a);
let chain = quotient.mul(&da_dy, dy);
quotient.add(&coeff_part, &chain)
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn rat(n: i64) -> Rational {
Rational::from(n)
}
fn inv_monomial(c: i64, k: usize) -> RatFn {
let mut den = vec![Rational::from(0); k + 1];
den[k] = Rational::from(c);
RatFn::new(vec![Rational::from(1)], den)
}
#[test]
fn ratfn_canonicalizes() {
let r = RatFn::new(vec![rat(0), rat(2)], vec![rat(2)]);
assert_eq!(r, RatFn::from_poly(&vec![rat(0), rat(1)]));
let z = RatFn::new(vec![], vec![rat(0), rat(0), rat(1)]);
assert!(z.is_zero());
assert_eq!(z.denom(), &poly_one());
}
#[test]
fn ratfn_field_derivation_quotient_rule() {
let f = RationalFunctionField;
let one_over_x = inv_monomial(1, 1);
let d = f.derivation(&one_over_x);
assert_eq!(d, RatFn::new(vec![rat(-1)], vec![rat(0), rat(0), rat(1)]));
let x2 = RatFn::from_poly(&vec![rat(0), rat(0), rat(1)]);
assert_eq!(f.derivation(&x2), RatFn::from_poly(&vec![rat(0), rat(2)]));
}
#[test]
fn derivation_of_cbrt_x() {
let e = AlgExtension::new(&[
vec![rat(0), rat(-1)], vec![rat(0)],
vec![rat(0)],
vec![rat(1)],
]);
assert_eq!(e.degree(), 3);
let y = e.generator();
let dy = e.derivation(&y);
let expected = vec![RatFn::int(0), inv_monomial(3, 1), RatFn::int(0)];
assert!(e.elem_eq(&dy, &expected), "D(∛x) = y/(3x); got {dy:?}");
assert!(e.elem_eq(e.dy(), &expected));
}
#[test]
fn derivation_of_sqrt_x_matches_kpair() {
let e = AlgExtension::new(&[vec![rat(0), rat(-1)], vec![rat(0)], vec![rat(1)]]);
let y = e.generator();
let dy = e.derivation(&y);
let expected = vec![RatFn::int(0), inv_monomial(2, 1)];
assert!(e.elem_eq(&dy, &expected), "D(√x) = y/(2x); got {dy:?}");
}
#[test]
fn derivation_consistency_y_cubed_is_x() {
let e = AlgExtension::new(&[
vec![rat(0), rat(-1)], vec![rat(0)],
vec![rat(0)],
vec![rat(1)],
]);
let y = e.generator();
let y3 = e.mul(&e.mul(&y, &y), &y); let dy3 = e.derivation(&y3);
assert!(e.elem_eq(&dy3, &e.from_int(1)), "D(y³)=D(x)=1; got {dy3:?}");
}
#[test]
fn derivation_of_y_squared() {
let e = AlgExtension::new(&[
vec![rat(0), rat(-1)],
vec![rat(0)],
vec![rat(0)],
vec![rat(1)],
]);
let y = e.generator();
let y2 = e.mul(&y, &y);
let dy2 = e.derivation(&y2);
let two_over_3x = RatFn::new(vec![rat(2)], vec![rat(0), rat(3)]);
let expected = vec![RatFn::int(0), RatFn::int(0), two_over_3x];
assert!(e.elem_eq(&dy2, &expected), "D(y²); got {dy2:?}");
}
#[test]
fn leibniz_rule_deterministic() {
let e = AlgExtension::new(&[
vec![rat(0), rat(-1)],
vec![rat(0)],
vec![rat(0)],
vec![rat(1)],
]);
let alpha = vec![RatFn::from_poly(&vec![rat(0), rat(1)]), RatFn::int(1)];
let beta = vec![inv_monomial(1, 1).neg(), RatFn::int(0), RatFn::int(1)];
let lhs = e.derivation(&e.mul(&alpha, &beta));
let rhs = e.add(
&e.mul(&e.derivation(&alpha), &beta),
&e.mul(&alpha, &e.derivation(&beta)),
);
assert!(e.elem_eq(&lhs, &rhs), "Leibniz: {lhs:?} vs {rhs:?}");
}
fn small_ratfn() -> impl Strategy<Value = RatFn> {
let coeff = -3i64..=3;
let nz = prop_oneof![1i64..=3, -3i64..=-1];
(
prop::collection::vec(coeff.clone(), 0..=2),
prop::collection::vec(coeff, 0..=1),
nz,
)
.prop_map(|(n, d, lead)| {
let num: QPoly = n.into_iter().map(Rational::from).collect();
let mut den: QPoly = d.into_iter().map(Rational::from).collect();
den.push(Rational::from(lead)); RatFn::new(num, den)
})
}
fn small_elem() -> impl Strategy<Value = AlgElem> {
prop::collection::vec(small_ratfn(), 0..=3)
}
fn cbrt_ext() -> AlgExtension {
AlgExtension::new(&[
vec![rat(0), rat(-1)],
vec![rat(0)],
vec![rat(0)],
vec![rat(1)],
])
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn prop_ring_axioms(a in small_elem(), b in small_elem(), c in small_elem()) {
let e = cbrt_ext();
prop_assert!(e.elem_eq(&e.add(&a, &b), &e.add(&b, &a)));
prop_assert!(e.elem_eq(&e.mul(&a, &b), &e.mul(&b, &a)));
prop_assert!(e.elem_eq(&e.add(&a, &e.from_int(0)), &a));
prop_assert!(e.is_zero(&e.add(&a, &e.neg(&a))));
prop_assert!(e.elem_eq(&e.mul(&a, &e.from_int(1)), &a));
let lhs = e.mul(&a, &e.add(&b, &c));
let rhs = e.add(&e.mul(&a, &b), &e.mul(&a, &c));
prop_assert!(e.elem_eq(&lhs, &rhs));
}
#[test]
fn prop_leibniz(a in small_elem(), b in small_elem()) {
let e = cbrt_ext();
let lhs = e.derivation(&e.mul(&a, &b));
let rhs = e.add(
&e.mul(&e.derivation(&a), &b),
&e.mul(&a, &e.derivation(&b)),
);
prop_assert!(e.elem_eq(&lhs, &rhs));
}
#[test]
fn prop_derivation_is_additive(a in small_elem(), b in small_elem()) {
let e = cbrt_ext();
let lhs = e.derivation(&e.add(&a, &b));
let rhs = e.add(&e.derivation(&a), &e.derivation(&b));
prop_assert!(e.elem_eq(&lhs, &rhs));
}
}
}