use rug::Rational;
use super::poly_rde::{trim, QPoly};
pub trait CoeffField {
type Elem: Clone + std::fmt::Debug;
fn zero(&self) -> Self::Elem;
fn one(&self) -> Self::Elem;
#[allow(clippy::wrong_self_convention)]
fn from_i64(&self, n: i64) -> Self::Elem;
fn add(&self, a: &Self::Elem, b: &Self::Elem) -> Self::Elem;
fn sub(&self, a: &Self::Elem, b: &Self::Elem) -> Self::Elem;
fn mul(&self, a: &Self::Elem, b: &Self::Elem) -> Self::Elem;
fn neg(&self, a: &Self::Elem) -> Self::Elem;
fn inv(&self, a: &Self::Elem) -> Option<Self::Elem>;
fn is_zero(&self, a: &Self::Elem) -> bool;
fn eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool;
fn derivation(&self, a: &Self::Elem) -> Self::Elem {
let _ = a;
self.zero()
}
}
#[derive(Clone, Debug, Default)]
pub struct RationalField;
impl CoeffField for RationalField {
type Elem = Rational;
fn zero(&self) -> Rational {
Rational::from(0)
}
fn one(&self) -> Rational {
Rational::from(1)
}
fn from_i64(&self, n: i64) -> Rational {
Rational::from(n)
}
fn add(&self, a: &Rational, b: &Rational) -> Rational {
Rational::from(a + b)
}
fn sub(&self, a: &Rational, b: &Rational) -> Rational {
Rational::from(a - b)
}
fn mul(&self, a: &Rational, b: &Rational) -> Rational {
Rational::from(a * b)
}
fn neg(&self, a: &Rational) -> Rational {
Rational::from(-a)
}
fn inv(&self, a: &Rational) -> Option<Rational> {
if *a == 0 {
None
} else {
Some(Rational::from(1) / a.clone())
}
}
fn is_zero(&self, a: &Rational) -> bool {
*a == 0
}
fn eq(&self, a: &Rational, b: &Rational) -> bool {
a == b
}
}
pub type GPoly<F> = Vec<<F as CoeffField>::Elem>;
pub type KPolyG<F> = Vec<GPoly<F>>;
pub fn gtrim<F: CoeffField>(f: &F, mut p: GPoly<F>) -> GPoly<F> {
while p.last().is_some_and(|c| f.is_zero(c)) {
p.pop();
}
p
}
pub fn gdegree<F: CoeffField>(f: &F, p: &[F::Elem]) -> i64 {
let mut d = p.len() as i64 - 1;
while d >= 0 && f.is_zero(&p[d as usize]) {
d -= 1;
}
d
}
pub fn gpoly_zero<F: CoeffField>() -> GPoly<F> {
Vec::new()
}
pub fn gpoly_one<F: CoeffField>(f: &F) -> GPoly<F> {
vec![f.one()]
}
pub fn gpoly_add<F: CoeffField>(f: &F, a: &[F::Elem], b: &[F::Elem]) -> GPoly<F> {
let n = a.len().max(b.len());
let mut r: GPoly<F> = (0..n).map(|_| f.zero()).collect();
for (i, c) in a.iter().enumerate() {
r[i] = f.add(&r[i], c);
}
for (i, c) in b.iter().enumerate() {
r[i] = f.add(&r[i], c);
}
gtrim(f, r)
}
pub fn gpoly_sub<F: CoeffField>(f: &F, a: &[F::Elem], b: &[F::Elem]) -> GPoly<F> {
let n = a.len().max(b.len());
let mut r: GPoly<F> = (0..n).map(|_| f.zero()).collect();
for (i, c) in a.iter().enumerate() {
r[i] = f.add(&r[i], c);
}
for (i, c) in b.iter().enumerate() {
r[i] = f.sub(&r[i], c);
}
gtrim(f, r)
}
pub fn gpoly_scale<F: CoeffField>(f: &F, p: &[F::Elem], s: &F::Elem) -> GPoly<F> {
if f.is_zero(s) || p.is_empty() {
return Vec::new();
}
gtrim(f, p.iter().map(|c| f.mul(c, s)).collect())
}
pub fn gpoly_mul<F: CoeffField>(f: &F, a: &[F::Elem], b: &[F::Elem]) -> GPoly<F> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut r: GPoly<F> = (0..a.len() + b.len() - 1).map(|_| f.zero()).collect();
for (i, ca) in a.iter().enumerate() {
if f.is_zero(ca) {
continue;
}
for (j, cb) in b.iter().enumerate() {
let p = f.mul(ca, cb);
r[i + j] = f.add(&r[i + j], &p);
}
}
gtrim(f, r)
}
pub fn gpoly_divrem<F: CoeffField>(f: &F, a: &[F::Elem], b: &[F::Elem]) -> (GPoly<F>, GPoly<F>) {
let b = gtrim(f, b.to_vec());
let bd = gdegree(f, &b);
debug_assert!(bd >= 0, "gpoly_divrem: division by zero polynomial");
let lc_inv = f
.inv(&b[bd as usize])
.expect("leading coefficient of a field element is invertible");
let mut r = gtrim(f, a.to_vec());
let ad = gdegree(f, &r);
if ad < bd {
return (Vec::new(), r);
}
let mut q: GPoly<F> = (0..(ad - bd + 1) as usize).map(|_| f.zero()).collect();
loop {
let rd = gdegree(f, &r);
if rd < bd {
break;
}
let shift = (rd - bd) as usize;
let factor = f.mul(&r[rd as usize], &lc_inv);
q[shift] = f.add(&q[shift], &factor);
for (i, bc) in b.iter().enumerate() {
let prod = f.mul(&factor, bc);
r[shift + i] = f.sub(&r[shift + i], &prod);
}
r = gtrim(f, r);
if r.is_empty() {
break;
}
}
(gtrim(f, q), r)
}
pub fn gext_gcd<F: CoeffField>(
f: &F,
a: &[F::Elem],
b: &[F::Elem],
) -> (GPoly<F>, GPoly<F>, GPoly<F>) {
let (mut old_r, mut r) = (gtrim(f, a.to_vec()), gtrim(f, b.to_vec()));
let (mut old_s, mut s) = (gpoly_one(f), gpoly_zero::<F>());
let (mut old_t, mut t) = (gpoly_zero::<F>(), gpoly_one(f));
while !r.is_empty() {
let (q, rem) = gpoly_divrem(f, &old_r, &r);
old_r = r;
r = rem;
let ns = gpoly_sub(f, &old_s, &gpoly_mul(f, &q, &s));
old_s = s;
s = ns;
let nt = gpoly_sub(f, &old_t, &gpoly_mul(f, &q, &t));
old_t = t;
t = nt;
}
let dg = gdegree(f, &old_r);
if dg < 0 {
return (Vec::new(), old_s, old_t);
}
let inv = f
.inv(&old_r[dg as usize])
.expect("nonzero leading coefficient of a field element is invertible");
(
gpoly_scale(f, &old_r, &inv),
gpoly_scale(f, &old_s, &inv),
gpoly_scale(f, &old_t, &inv),
)
}
pub fn gpoly_mod<F: CoeffField>(f: &F, a: &[F::Elem], m: &[F::Elem]) -> GPoly<F> {
gpoly_divrem(f, a, m).1
}
pub fn gmod_inverse<F: CoeffField>(f: &F, w: &[F::Elem], v: &[F::Elem]) -> Option<GPoly<F>> {
let (g, s, _t) = gext_gcd(f, w, v);
if gdegree(f, &g) != 0 {
return None; }
Some(gpoly_mod(f, &s, v))
}
pub fn gpoly_eq<F: CoeffField>(f: &F, a: &[F::Elem], b: &[F::Elem]) -> bool {
let a = gtrim(f, a.to_vec());
let b = gtrim(f, b.to_vec());
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| f.eq(x, y))
}
pub fn poly_mod(a: &QPoly, m: &QPoly) -> QPoly {
gpoly_mod(&RationalField, a, m)
}
pub fn ext_gcd(a: &QPoly, b: &QPoly) -> (QPoly, QPoly, QPoly) {
gext_gcd(&RationalField, a, b)
}
pub fn mod_inverse(w: &QPoly, v: &QPoly) -> Option<QPoly> {
gmod_inverse(&RationalField, w, v)
}
#[derive(Clone, Debug)]
pub struct Quotient<F: CoeffField> {
field: F,
modulus: GPoly<F>,
}
impl<F: CoeffField> Quotient<F> {
pub fn new(field: F, modulus: GPoly<F>) -> Self {
let modulus = gtrim(&field, modulus);
Self { field, modulus }
}
pub fn field(&self) -> &F {
&self.field
}
pub fn modulus(&self) -> &GPoly<F> {
&self.modulus
}
pub fn degree(&self) -> i64 {
gdegree(&self.field, &self.modulus)
}
pub fn elem_zero() -> GPoly<F> {
Vec::new()
}
pub fn elem_is_zero(&self, a: &[F::Elem]) -> bool {
gtrim(&self.field, a.to_vec()).is_empty()
}
pub fn elem_eq(&self, a: &[F::Elem], b: &[F::Elem]) -> bool {
gpoly_eq(&self.field, a, b)
}
pub fn reduce(&self, a: &[F::Elem]) -> GPoly<F> {
gpoly_mod(&self.field, a, &self.modulus)
}
pub fn add(&self, a: &[F::Elem], b: &[F::Elem]) -> GPoly<F> {
self.reduce(&gpoly_add(&self.field, a, b))
}
pub fn sub(&self, a: &[F::Elem], b: &[F::Elem]) -> GPoly<F> {
self.reduce(&gpoly_sub(&self.field, a, b))
}
pub fn mul(&self, a: &[F::Elem], b: &[F::Elem]) -> GPoly<F> {
self.reduce(&gpoly_mul(&self.field, a, b))
}
pub fn neg(&self, a: &[F::Elem]) -> GPoly<F> {
let neg_one = self.field.neg(&self.field.one());
self.reduce(&gpoly_scale(&self.field, a, &neg_one))
}
pub fn inv(&self, a: &[F::Elem]) -> Option<GPoly<F>> {
gmod_inverse(&self.field, a, &self.modulus)
}
pub fn from_int(&self, n: i64) -> GPoly<F> {
self.reduce(&[self.field.from_i64(n)])
}
pub fn kdeg(&self, p: &[GPoly<F>]) -> i64 {
let mut d = p.len() as i64 - 1;
while d >= 0 && self.elem_is_zero(&p[d as usize]) {
d -= 1;
}
d
}
pub fn kpoly_trim(&self, mut p: Vec<GPoly<F>>) -> Vec<GPoly<F>> {
while p.last().is_some_and(|c| self.elem_is_zero(c)) {
p.pop();
}
p
}
pub fn kcoeff(&self, p: &[GPoly<F>], i: i64) -> GPoly<F> {
if i < 0 {
return Self::elem_zero();
}
p.get(i as usize).cloned().unwrap_or_else(Self::elem_zero)
}
pub fn kpoly_divrem(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> Option<(KPolyG<F>, KPolyG<F>)> {
let bd = self.kdeg(b);
if bd < 0 {
return None;
}
let lead_inv = self.inv(&b[bd as usize])?;
let mut r = self.kpoly_trim(a.to_vec());
let ad = self.kdeg(&r);
if ad < bd {
return Some((vec![], r));
}
let mut quot = vec![Self::elem_zero(); (ad - bd + 1) as usize];
loop {
let rd = self.kdeg(&r);
if rd < bd {
break;
}
let coeff = self.mul(&r[rd as usize], &lead_inv);
let shift = (rd - bd) as usize;
for (i, bi) in b.iter().enumerate() {
if shift + i < r.len() {
r[shift + i] = self.sub(&r[shift + i], &self.mul(&coeff, bi));
}
}
quot[shift] = coeff;
r = self.kpoly_trim(r);
if r.is_empty() {
break;
}
}
Some((self.kpoly_trim(quot), r))
}
pub fn kpoly_gcd(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> Option<Vec<GPoly<F>>> {
let mut a = self.kpoly_trim(a.to_vec());
let mut b = self.kpoly_trim(b.to_vec());
while self.kdeg(&b) >= 0 {
let (_, rem) = self.kpoly_divrem(&a, &b)?;
a = b;
b = rem;
}
let ad = self.kdeg(&a);
if ad < 0 {
return None;
}
let lead_inv = self.inv(&a[ad as usize])?;
Some(a.iter().map(|c| self.mul(c, &lead_inv)).collect())
}
pub fn kpoly_add(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> Vec<GPoly<F>> {
let n = a.len().max(b.len());
let mut r = vec![Self::elem_zero(); n];
for (i, c) in a.iter().enumerate() {
r[i] = self.add(&r[i], c);
}
for (i, c) in b.iter().enumerate() {
r[i] = self.add(&r[i], c);
}
self.kpoly_trim(r)
}
pub fn kpoly_sub(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> Vec<GPoly<F>> {
let n = a.len().max(b.len());
let mut r = vec![Self::elem_zero(); n];
for (i, c) in a.iter().enumerate() {
r[i] = self.add(&r[i], c);
}
for (i, c) in b.iter().enumerate() {
r[i] = self.sub(&r[i], c);
}
self.kpoly_trim(r)
}
pub fn kpoly_scale(&self, p: &[GPoly<F>], s: &[F::Elem]) -> Vec<GPoly<F>> {
if self.elem_is_zero(s) {
return Vec::new();
}
self.kpoly_trim(p.iter().map(|c| self.mul(c, s)).collect())
}
pub fn kpoly_mul(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> Vec<GPoly<F>> {
if self.kdeg(a) < 0 || self.kdeg(b) < 0 {
return Vec::new();
}
let mut r = vec![Self::elem_zero(); a.len() + b.len() - 1];
for (i, ca) in a.iter().enumerate() {
if self.elem_is_zero(ca) {
continue;
}
for (j, cb) in b.iter().enumerate() {
let p = self.mul(ca, cb);
r[i + j] = self.add(&r[i + j], &p);
}
}
self.kpoly_trim(r)
}
pub fn kpoly_deriv(&self, p: &[GPoly<F>]) -> Vec<GPoly<F>> {
if p.len() <= 1 {
return Vec::new();
}
self.kpoly_trim(
p[1..]
.iter()
.enumerate()
.map(|(i, c)| self.mul(&self.from_int(i as i64 + 1), c))
.collect(),
)
}
pub fn kpoly_integrate(&self, p: &[GPoly<F>]) -> Vec<GPoly<F>> {
let p = self.kpoly_trim(p.to_vec());
if p.is_empty() {
return Vec::new();
}
let mut r = vec![Self::elem_zero()];
for (i, c) in p.iter().enumerate() {
let inv = self
.inv(&self.from_int(i as i64 + 1))
.expect("nonzero integer is invertible in a field");
r.push(self.mul(c, &inv));
}
self.kpoly_trim(r)
}
pub fn kpoly_pow(&self, p: &[GPoly<F>], n: u32) -> Vec<GPoly<F>> {
let mut acc = vec![self.from_int(1)];
for _ in 0..n {
acc = self.kpoly_mul(&acc, p);
}
acc
}
pub fn kpoly_monic(&self, p: &[GPoly<F>]) -> Option<Vec<GPoly<F>>> {
let d = self.kdeg(p);
if d < 0 {
return Some(Vec::new());
}
let inv = self.inv(&p[d as usize])?;
Some(self.kpoly_trim(p.iter().map(|c| self.mul(c, &inv)).collect()))
}
pub fn kpoly_div_exact(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> Option<Vec<GPoly<F>>> {
let (q, r) = self.kpoly_divrem(a, b)?;
if self.kdeg(&r) >= 0 {
return None;
}
Some(q)
}
pub fn kpoly_eq(&self, a: &[GPoly<F>], b: &[GPoly<F>]) -> bool {
let a = self.kpoly_trim(a.to_vec());
let b = self.kpoly_trim(b.to_vec());
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| self.elem_eq(x, y))
}
}
pub type KElem = QPoly;
pub type KPoly = Vec<KElem>;
#[derive(Clone, Debug)]
pub struct NumberField {
inner: Quotient<RationalField>,
}
impl NumberField {
pub fn new(modulus: QPoly) -> Self {
Self {
inner: Quotient::new(RationalField, modulus),
}
}
pub fn quotient(&self) -> &Quotient<RationalField> {
&self.inner
}
pub fn modulus(&self) -> &QPoly {
self.inner.modulus()
}
pub fn degree(&self) -> i64 {
self.inner.degree()
}
pub fn field_degree(&self) -> i64 {
self.inner.degree()
}
pub fn reduce(&self, a: &KElem) -> KElem {
self.inner.reduce(a)
}
pub fn add(&self, a: &KElem, b: &KElem) -> KElem {
self.inner.add(a, b)
}
pub fn sub(&self, a: &KElem, b: &KElem) -> KElem {
self.inner.sub(a, b)
}
pub fn mul(&self, a: &KElem, b: &KElem) -> KElem {
self.inner.mul(a, b)
}
pub fn neg(&self, a: &KElem) -> KElem {
self.inner.neg(a)
}
pub fn inv(&self, a: &KElem) -> Option<KElem> {
self.inner.inv(a)
}
pub fn is_zero(a: &KElem) -> bool {
trim(a.clone()).is_empty()
}
pub fn kdeg(p: &[KElem]) -> i64 {
let mut d = p.len() as i64 - 1;
while d >= 0 && Self::is_zero(&p[d as usize]) {
d -= 1;
}
d
}
pub fn kpoly_trim(mut p: KPoly) -> KPoly {
while p.last().is_some_and(Self::is_zero) {
p.pop();
}
p
}
pub fn kpoly_divrem(&self, a: &[KElem], b: &[KElem]) -> Option<(KPoly, KPoly)> {
self.inner.kpoly_divrem(a, b)
}
pub fn kpoly_gcd(&self, a: &[KElem], b: &[KElem]) -> Option<KPoly> {
self.inner.kpoly_gcd(a, b)
}
pub fn k_zero() -> KElem {
Vec::new()
}
pub fn from_int(&self, n: i64) -> KElem {
self.inner.from_int(n)
}
pub fn from_rational(&self, r: &Rational) -> KElem {
self.inner.reduce(std::slice::from_ref(r))
}
pub fn kpoly_add(&self, a: &[KElem], b: &[KElem]) -> KPoly {
self.inner.kpoly_add(a, b)
}
pub fn kpoly_sub(&self, a: &[KElem], b: &[KElem]) -> KPoly {
self.inner.kpoly_sub(a, b)
}
pub fn kpoly_scale(&self, p: &[KElem], s: &KElem) -> KPoly {
self.inner.kpoly_scale(p, s)
}
pub fn kpoly_mul(&self, a: &[KElem], b: &[KElem]) -> KPoly {
self.inner.kpoly_mul(a, b)
}
pub fn kpoly_deriv(&self, p: &[KElem]) -> KPoly {
self.inner.kpoly_deriv(p)
}
pub fn kpoly_integrate(&self, p: &[KElem]) -> KPoly {
self.inner.kpoly_integrate(p)
}
pub fn kpoly_pow(&self, p: &[KElem], n: u32) -> KPoly {
self.inner.kpoly_pow(p, n)
}
pub fn kpoly_monic(&self, p: &[KElem]) -> Option<KPoly> {
self.inner.kpoly_monic(p)
}
pub fn kpoly_div_exact(&self, a: &[KElem], b: &[KElem]) -> Option<KPoly> {
self.inner.kpoly_div_exact(a, b)
}
pub fn kcoeff(p: &[KElem], i: i64) -> KElem {
if i < 0 {
return Self::k_zero();
}
p.get(i as usize).cloned().unwrap_or_else(Self::k_zero)
}
pub fn kpoly_eq(a: &[KElem], b: &[KElem]) -> bool {
let a = Self::kpoly_trim(a.to_vec());
let b = Self::kpoly_trim(b.to_vec());
if a.len() != b.len() {
return false;
}
a.iter()
.zip(b.iter())
.all(|(x, y)| trim(x.clone()) == trim(y.clone()))
}
fn mult_matrix(&self, a: &KElem) -> Vec<Vec<Rational>> {
let d = self.degree();
if d < 1 {
let c = self.reduce(a);
let v = c.first().cloned().unwrap_or_else(|| Rational::from(0));
return vec![vec![v]];
}
let d = d as usize;
let a_red = self.reduce(a);
let mut m = vec![vec![Rational::from(0); d]; d];
for j in 0..d {
let mut mono = vec![Rational::from(0); j + 1];
mono[j] = Rational::from(1);
let col = self.mul(&a_red, &mono);
for (i, row) in m.iter_mut().enumerate() {
row[j] = col.get(i).cloned().unwrap_or_else(|| Rational::from(0));
}
}
m
}
pub fn norm(&self, a: &KElem) -> Rational {
rat_det(self.mult_matrix(a))
}
pub fn trace(&self, a: &KElem) -> Rational {
let m = self.mult_matrix(a);
m.iter()
.enumerate()
.fold(Rational::from(0), |s, (i, row)| s + row[i].clone())
}
}
fn rat_det(mut m: Vec<Vec<Rational>>) -> Rational {
let n = m.len();
let mut det = Rational::from(1);
for col in 0..n {
let Some(piv) = (col..n).find(|&r| m[r][col] != 0) else {
return Rational::from(0);
};
if piv != col {
m.swap(piv, col);
det = -det;
}
let pivot = m[col][col].clone();
det *= pivot.clone();
let pivot_row: Vec<Rational> = m[col][col..n].to_vec();
for row in m.iter_mut().skip(col + 1) {
if row[col] != 0 {
let factor = row[col].clone() / pivot.clone();
for (dst, src) in row[col..n].iter_mut().zip(pivot_row.iter()) {
*dst -= factor.clone() * src.clone();
}
}
}
}
det
}
#[cfg(test)]
mod tests {
use super::*;
use crate::integrate::risch::poly_rde::{poly_mul, poly_zero};
fn rat(n: i64) -> Rational {
Rational::from(n)
}
fn frac(n: i64, d: i64) -> Rational {
Rational::from((n, d))
}
fn q_sqrt2() -> NumberField {
NumberField::new(vec![rat(-2), rat(0), rat(1)])
}
#[test]
fn base_field_mod_inverse() {
let m = vec![rat(1), rat(0), rat(1)]; let x = vec![rat(0), rat(1)];
let inv = mod_inverse(&x, &m).expect("x is a unit mod x²+1");
assert_eq!(trim(poly_mod(&poly_mul(&x, &inv), &m)), vec![rat(1)]);
}
#[test]
fn sqrt2_arithmetic() {
let k = q_sqrt2();
let t = vec![rat(0), rat(1)]; let one_plus = k.add(&vec![rat(1)], &t);
let sq = k.mul(&one_plus, &one_plus);
assert_eq!(trim(sq), vec![rat(3), rat(2)]);
let inv = k.inv(&t).expect("√2 invertible");
assert_eq!(trim(inv.clone()), vec![rat(0), frac(1, 2)]);
assert_eq!(trim(k.mul(&t, &inv)), vec![rat(1)]);
}
#[test]
fn kpoly_gcd_splits_over_sqrt2() {
let k = q_sqrt2();
let neg_t = k.neg(&vec![rat(0), rat(1)]); let a: KPoly = vec![vec![rat(-2)], poly_zero(), vec![rat(1)]];
let b: KPoly = vec![neg_t.clone(), vec![rat(1)]];
let g = k.kpoly_gcd(&a, &b).expect("gcd exists");
assert_eq!(NumberField::kdeg(&g), 1);
assert_eq!(trim(g[0].clone()), trim(neg_t));
assert_eq!(trim(g[1].clone()), vec![rat(1)]);
}
#[test]
fn non_unit_leading_coeff_is_none() {
let k = NumberField::new(vec![rat(-1), rat(0), rat(1)]);
assert!(k.inv(&vec![rat(-1), rat(1)]).is_none());
}
#[test]
fn kpoly_mul_over_sqrt2() {
let k = q_sqrt2();
let t = vec![rat(0), rat(1)]; let a = vec![t.clone(), vec![rat(1)]]; let b = vec![k.neg(&t), vec![rat(1)]]; let p = k.kpoly_mul(&a, &b);
assert_eq!(NumberField::kdeg(&p), 2);
assert_eq!(trim(p[0].clone()), vec![rat(-2)]); assert!(NumberField::is_zero(&p[1])); assert_eq!(trim(p[2].clone()), vec![rat(1)]); }
#[test]
fn kpoly_deriv_integrate_roundtrip() {
let k = q_sqrt2();
let t = vec![rat(0), rat(1)];
let p = vec![NumberField::k_zero(), vec![rat(1)], t.clone()]; let integ = k.kpoly_integrate(&p);
let back = k.kpoly_deriv(&integ);
let p_t = NumberField::kpoly_trim(p);
let back_t = NumberField::kpoly_trim(back);
assert_eq!(p_t.len(), back_t.len());
for (a, b) in p_t.iter().zip(back_t.iter()) {
assert_eq!(trim(a.clone()), trim(b.clone()));
}
}
#[test]
fn rational_field_derivation_is_zero() {
let f = RationalField;
assert!(f.is_zero(&f.derivation(&rat(5))));
assert!(f.is_zero(&f.derivation(&frac(3, 7))));
}
#[test]
fn norm_and_trace_over_sqrt2() {
let k = q_sqrt2();
assert_eq!(k.field_degree(), 2);
let t = vec![rat(0), rat(1)]; assert_eq!(k.norm(&t), rat(-2));
assert_eq!(k.trace(&t), rat(0));
let one_plus = vec![rat(1), rat(1)];
assert_eq!(k.norm(&one_plus), rat(-1));
assert_eq!(k.trace(&one_plus), rat(2));
let three = vec![rat(3)];
assert_eq!(k.norm(&three), rat(9));
assert_eq!(k.trace(&three), rat(6));
}
#[test]
fn norm_over_cube_root_field() {
let k = NumberField::new(vec![rat(-2), rat(0), rat(0), rat(1)]);
assert_eq!(k.field_degree(), 3);
let cbrt2 = vec![rat(0), rat(1)];
assert_eq!(k.norm(&cbrt2), rat(2));
assert_eq!(k.trace(&cbrt2), rat(0));
}
#[derive(Clone, Debug)]
struct Fp {
p: i64,
}
fn modp(a: i64, p: i64) -> i64 {
((a % p) + p) % p
}
fn modpow(mut b: i64, mut e: i64, p: i64) -> i64 {
let mut acc = 1i64;
b = modp(b, p);
while e > 0 {
if e & 1 == 1 {
acc = modp(acc * b, p);
}
b = modp(b * b, p);
e >>= 1;
}
acc
}
impl CoeffField for Fp {
type Elem = i64;
fn zero(&self) -> i64 {
0
}
fn one(&self) -> i64 {
1 % self.p
}
fn from_i64(&self, n: i64) -> i64 {
modp(n, self.p)
}
fn add(&self, a: &i64, b: &i64) -> i64 {
modp(a + b, self.p)
}
fn sub(&self, a: &i64, b: &i64) -> i64 {
modp(a - b, self.p)
}
fn mul(&self, a: &i64, b: &i64) -> i64 {
modp(a * b, self.p)
}
fn neg(&self, a: &i64) -> i64 {
modp(-a, self.p)
}
fn inv(&self, a: &i64) -> Option<i64> {
if modp(*a, self.p) == 0 {
None
} else {
Some(modpow(*a, self.p - 2, self.p)) }
}
fn is_zero(&self, a: &i64) -> bool {
modp(*a, self.p) == 0
}
fn eq(&self, a: &i64, b: &i64) -> bool {
modp(a - b, self.p) == 0
}
}
#[test]
fn generic_gcd_over_f5() {
let f = Fp { p: 5 };
let a = vec![f.from_i64(-1), f.from_i64(0), f.from_i64(1)]; let b = vec![f.from_i64(-1), f.from_i64(1)]; let (g, _s, _t) = gext_gcd(&f, &a, &b);
assert_eq!(gdegree(&f, &g), 1);
assert_eq!(g, vec![4, 1]);
assert!(gpoly_mod(&f, &a, &g).is_empty() || gdegree(&f, &gpoly_mod(&f, &a, &g)) < 0);
assert!(gdegree(&f, &gpoly_mod(&f, &b, &g)) < 0);
}
#[test]
fn generic_quotient_over_f7() {
let f = Fp { p: 7 };
let q = Quotient::new(f.clone(), vec![f.from_i64(1), f.from_i64(0), f.from_i64(1)]);
let t = vec![0i64, 1];
let tt = q.mul(&t, &t);
assert!(q.elem_eq(&tt, &[6])); let inv = q.inv(&t).expect("t invertible mod t²+1");
assert!(q.elem_eq(&q.mul(&t, &inv), &[1]));
}
#[test]
fn numberfield_matches_generic_quotient() {
let modulus = vec![rat(-2), rat(0), rat(1)]; let nf = NumberField::new(modulus.clone());
let q = Quotient::new(RationalField, modulus);
let a = vec![rat(1), rat(1)]; assert_eq!(nf.mul(&a, &a), q.mul(&a, &a));
assert_eq!(nf.inv(&a), q.inv(&a));
}
}