From 77f3a66337c810da2da0f526c3eea3b8379567b4 Mon Sep 17 00:00:00 2001 From: Ben Ruijl Date: Sun, 14 Jul 2024 12:13:22 +0200 Subject: [PATCH] Add algebraic numbers and Galois fields to Python API --- src/api/python.rs | 346 ++++++++++++++++++++++++++------ src/domains/algebraic_number.rs | 36 ++-- src/domains/finite_field.rs | 3 +- src/domains/rational.rs | 24 ++- src/poly.rs | 70 +++++++ src/poly/factor.rs | 13 +- src/poly/groebner.rs | 7 +- symbolica.pyi | 219 +++++++++++++++++++- 8 files changed, 630 insertions(+), 88 deletions(-) diff --git a/src/api/python.rs b/src/api/python.rs index 699ac83..850706d 100644 --- a/src/api/python.rs +++ b/src/api/python.rs @@ -27,10 +27,11 @@ use smartstring::{LazyCompact, SmartString}; use crate::{ atom::{Atom, AtomType, AtomView, ListIterator, Symbol}, domains::{ + algebraic_number::AlgebraicExtension, atom::AtomField, - finite_field::{ToFiniteField, Zp}, + finite_field::{ToFiniteField, Zp, Z2}, float::{Complex, Float}, - integer::{Integer, IntegerRing, Z}, + integer::{FromFiniteField, Integer, IntegerRing, Z}, rational::{Rational, RationalField, Q}, rational_polynomial::{ FromNumeratorAndDenominator, RationalPolynomial, RationalPolynomialField, @@ -72,6 +73,7 @@ fn symbolica(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -2914,7 +2916,22 @@ impl PythonExpression { /// Convert the expression to a polynomial, optionally, with the variables and the ordering specified in `vars`. /// All non-polynomial elements will be converted to new independent variables. - pub fn to_polynomial(&self, vars: Option>) -> PyResult { + /// + /// If a `modulus` is provided, the coefficients will be converted to finite field elements modulo `modulus`. + /// If on top an `extension` is provided, for example `(2, a)`, the polynomial will be converted to the Galois field + /// `GF(modulus^2)` where `a` is the variable of the minimal polynomial of the field. + /// + /// If a `minimal_poly` is provided, the polynomial will be converted to a number field with the given minimal polynomial. + /// The minimal polynomial must be a monic, irreducible univariate polynomial. If a `modulus` is provided as well, + /// the Galois field will be created with `minimal_poly` as the minimal polynomial. + pub fn to_polynomial( + &self, + modulus: Option, + extension: Option<(u16, Symbol)>, + minimal_poly: Option, + vars: Option>, + py: Python, + ) -> PyResult { let mut var_map = vec![]; if let Some(vm) = vars { for v in vm { @@ -2936,9 +2953,108 @@ impl PythonExpression { Some(Arc::new(var_map)) }; - Ok(PythonPolynomial { - poly: self.expr.to_polynomial(&Q, var_map), - }) + if extension.is_some() && modulus.is_none() { + return Err(exceptions::PyValueError::new_err( + "Extension field requires a modulus to be set", + )); + } + + let poly = minimal_poly.map(|p| p.expr.to_polynomial::<_, u16>(&Q, None)); + if let Some(p) = &poly { + if p.nvars() != 1 { + return Err(exceptions::PyValueError::new_err( + "Minimal polynomial must be a univariate polynomial", + )); + } + } + + if let Some(m) = modulus { + if let Some((e, name)) = extension { + if let Some(p) = &poly { + if e != p.degree(0) { + return Err(exceptions::PyValueError::new_err( + "Extension field degree must match the minimal polynomial degree", + )); + } + + if Variable::Symbol(name) != p.get_vars_ref()[0] { + return Err(exceptions::PyValueError::new_err( + "Extension variable must be the same as the variable in the minimal polynomial", + )); + } + + if m == 2 { + let p = p.map_coeff(|c| c.to_finite_field(&Z2), Z2); + if !p.is_irreducible() || e != p.degree(0) { + return Err(exceptions::PyValueError::new_err( + "Minimal polynomial must be irreducible and monic", + )); + } + + let g = AlgebraicExtension::new(p); + Ok(PythonGaloisFieldPrimeTwoPolynomial { + poly: self.expr.to_polynomial(&g, var_map), + } + .into_py(py)) + } else { + let f = Zp::new(m); + let p = p.map_coeff(|c| c.to_finite_field(&f), f.clone()); + if !p.is_irreducible() || !f.is_one(&p.lcoeff()) || e != p.degree(0) { + return Err(exceptions::PyValueError::new_err( + "Minimal polynomial must be irreducible and monic", + )); + } + + let g = AlgebraicExtension::new(p); + Ok(PythonGaloisFieldPolynomial { + poly: self.expr.to_polynomial(&g, var_map), + } + .into_py(py)) + } + } else if m == 2 { + let g = AlgebraicExtension::galois_field(Z2, e as usize, name.into()); + Ok(PythonGaloisFieldPrimeTwoPolynomial { + poly: self.expr.to_polynomial(&g, var_map), + } + .into_py(py)) + } else { + let g = AlgebraicExtension::galois_field(Zp::new(m), e as usize, name.into()); + Ok(PythonGaloisFieldPolynomial { + poly: self.expr.to_polynomial(&g, var_map), + } + .into_py(py)) + } + } else if m == 2 { + Ok(PythonPrimeTwoPolynomial { + poly: self.expr.to_polynomial(&Z2, var_map), + } + .into_py(py)) + } else { + Ok(PythonFiniteFieldPolynomial { + poly: self.expr.to_polynomial(&Zp::new(m), var_map), + } + .into_py(py)) + } + } else { + if let Some(p) = poly { + if !p.is_irreducible() || !p.lcoeff().is_one() { + return Err(exceptions::PyValueError::new_err( + "Minimal polynomial must be irreducible and monic", + )); + } + + let f = AlgebraicExtension::new(p); + Ok(PythonNumberFieldPolynomial { + poly: self.expr.to_polynomial(&Q, var_map).to_number_field(&f), + } + .into_py(py)) + } else { + Ok(PythonPolynomial { + poly: self.expr.to_polynomial(&Q, var_map), + } + .into_py(py)) + } + } } /// Convert the expression to a rational polynomial, optionally, with the variable ordering specified in `vars`. @@ -4366,67 +4482,170 @@ impl PythonFiniteFieldPolynomial { Ok(Self { poly: e }) } - /// Compute the Groebner basis of a polynomial system. - /// - /// If `grevlex=True`, reverse graded lexicographical ordering is used, - /// otherwise the ordering is lexicographical. - /// - /// If `print_stats=True` intermediate statistics will be printed. - #[pyo3(signature = (system, grevlex = true, print_stats = false))] - #[classmethod] - pub fn groebner_basis( - _cls: &PyType, - system: Vec, - grevlex: bool, - print_stats: bool, - ) -> Vec { - if grevlex { - let grevlex_ideal: Vec<_> = system - .iter() - .map(|p| p.poly.reorder::()) - .collect(); - let gb = GroebnerBasis::new(&grevlex_ideal, print_stats); + /// Convert the polynomial to an expression. + pub fn to_expression(&self) -> PyResult { + let p = self.poly.map_coeff( + |c| Integer::from_finite_field(&self.poly.field, c.clone()), + IntegerRing::new(), + ); - gb.system - .into_iter() - .map(|p| Self { - poly: p.reorder::(), + Ok(p.to_expression().into()) + } +} + +macro_rules! generate_field_methods { + ($type:ty, $exp_type:ty) => { + #[pymethods] + impl $type { + /// Compute the Groebner basis of a polynomial system. + /// + /// If `grevlex=True`, reverse graded lexicographical ordering is used, + /// otherwise the ordering is lexicographical. + /// + /// If `print_stats=True` intermediate statistics will be printed. + #[pyo3(signature = (system, grevlex = true, print_stats = false))] + #[classmethod] + pub fn groebner_basis( + _cls: &PyType, + system: Vec, + grevlex: bool, + print_stats: bool, + ) -> Vec { + if grevlex { + let grevlex_ideal: Vec<_> = system + .iter() + .map(|p| p.poly.reorder::()) + .collect(); + let gb = GroebnerBasis::new(&grevlex_ideal, print_stats); + + gb.system + .into_iter() + .map(|p| Self { + poly: p.reorder::(), + }) + .collect() + } else { + let ideal: Vec<_> = system.iter().map(|p| p.poly.clone()).collect(); + let gb = GroebnerBasis::new(&ideal, print_stats); + gb.system.into_iter().map(|p| Self { poly: p }).collect() + } + } + + /// Integrate the polynomial in `x`. + /// + /// Examples + /// -------- + /// + /// >>> from symbolica import Expression + /// >>> x = Expression.symbol('x') + /// >>> p = Expression.parse('x^2+2').to_polynomial() + /// >>> print(p.integrate(x)) + pub fn integrate(&self, x: PythonExpression) -> PyResult { + let x = self + .poly + .get_vars_ref() + .iter() + .position(|v| match (v, x.expr.as_view()) { + (Variable::Symbol(y), AtomView::Var(vv)) => *y == vv.get_symbol(), + (Variable::Function(_, f) | Variable::Other(f), a) => f.as_view() == a, + _ => false, + }) + .ok_or(exceptions::PyValueError::new_err(format!( + "Variable {} not found in polynomial", + x.__str__()? + )))?; + + Ok(Self { + poly: self.poly.integrate(x), }) - .collect() - } else { - let ideal: Vec<_> = system.iter().map(|p| p.poly.clone()).collect(); - let gb = GroebnerBasis::new(&ideal, print_stats); - gb.system.into_iter().map(|p| Self { poly: p }).collect() + } } + }; +} + +/// A Symbolica polynomial over Galois fields. +#[pyclass(name = "PrimeTwoPolynomial", module = "symbolica")] +#[derive(Clone)] +pub struct PythonPrimeTwoPolynomial { + pub poly: MultivariatePolynomial, +} + +#[pymethods] +impl PythonPrimeTwoPolynomial { + /// Convert the polynomial to an expression. + pub fn to_expression(&self) -> PyResult { + let p = self + .poly + .map_coeff(|c| (*c as i64).into(), IntegerRing::new()); + + Ok(p.to_expression().into()) } +} - /// Integrate the polynomial in `x`. - /// - /// Examples - /// -------- - /// - /// >>> from symbolica import Expression - /// >>> x = Expression.symbol('x') - /// >>> p = Expression.parse('x^2+2').to_polynomial() - /// >>> print(p.integrate(x)) - pub fn integrate(&self, x: PythonExpression) -> PyResult { - let x = self +/// A Symbolica polynomial over Z2 Galois fields. +#[pyclass(name = "GaloisFieldPrimeTwoPolynomial", module = "symbolica")] +#[derive(Clone)] +pub struct PythonGaloisFieldPrimeTwoPolynomial { + pub poly: MultivariatePolynomial, u16>, +} + +#[pymethods] +impl PythonGaloisFieldPrimeTwoPolynomial { + /// Convert the polynomial to an expression. + pub fn to_expression(&self) -> PyResult { + Ok(self .poly - .get_vars_ref() - .iter() - .position(|v| match (v, x.expr.as_view()) { - (Variable::Symbol(y), AtomView::Var(vv)) => *y == vv.get_symbol(), - (Variable::Function(_, f) | Variable::Other(f), a) => f.as_view() == a, - _ => false, + .to_expression_with_coeff_map(|_, element, out| { + let p = element + .poly + .map_coeff(|c| (*c as i64).into(), IntegerRing::new()); + p.to_expression_into(out); }) - .ok_or(exceptions::PyValueError::new_err(format!( - "Variable {} not found in polynomial", - x.__str__()? - )))?; + .into()) + } +} - Ok(Self { - poly: self.poly.integrate(x), - }) +/// A Symbolica polynomial over Galois fields. +#[pyclass(name = "GaloisFieldPolynomial", module = "symbolica")] +#[derive(Clone)] +pub struct PythonGaloisFieldPolynomial { + pub poly: MultivariatePolynomial, u16>, +} + +#[pymethods] +impl PythonGaloisFieldPolynomial { + /// Convert the polynomial to an expression. + pub fn to_expression(&self) -> PyResult { + Ok(self + .poly + .to_expression_with_coeff_map(|_, element, out| { + let p = element.poly.map_coeff( + |c| Integer::from_finite_field(&element.poly.field, c.clone()), + IntegerRing::new(), + ); + p.to_expression_into(out); + }) + .into()) + } +} + +/// A Symbolica polynomial over number fields. +#[pyclass(name = "NumberFieldPolynomial", module = "symbolica")] +#[derive(Clone)] +pub struct PythonNumberFieldPolynomial { + pub poly: MultivariatePolynomial, u16>, +} + +#[pymethods] +impl PythonNumberFieldPolynomial { + /// Convert the polynomial to an expression. + pub fn to_expression(&self) -> PyResult { + Ok(self + .poly + .to_expression_with_coeff_map(|_, element, out| { + element.poly.to_expression_into(out); + }) + .into()) } } @@ -4898,12 +5117,21 @@ macro_rules! generate_methods { } } } + }; } generate_methods!(PythonPolynomial, u16); generate_methods!(PythonIntegerPolynomial, u8); generate_methods!(PythonFiniteFieldPolynomial, u16); +generate_methods!(PythonPrimeTwoPolynomial, u16); +generate_methods!(PythonGaloisFieldPolynomial, u16); +generate_methods!(PythonNumberFieldPolynomial, u16); + +generate_field_methods!(PythonFiniteFieldPolynomial, u16); +generate_field_methods!(PythonPrimeTwoPolynomial, u16); +generate_field_methods!(PythonGaloisFieldPolynomial, u16); +generate_field_methods!(PythonNumberFieldPolynomial, u16); /// A Symbolica rational polynomial. #[pyclass(name = "RationalPolynomial", module = "symbolica")] diff --git a/src/domains/algebraic_number.rs b/src/domains/algebraic_number.rs index f863225..1622332 100644 --- a/src/domains/algebraic_number.rs +++ b/src/domains/algebraic_number.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, sync::Arc}; +use std::sync::Arc; use rand::Rng; @@ -25,7 +25,7 @@ use super::{ // TODO: make special case for degree two and three and hardcode the multiplication table #[derive(Clone, PartialEq, Eq, PartialOrd, Hash)] pub struct AlgebraicExtension { - poly: Rc>, // TODO: convert to univariate polynomial + poly: Arc>, // TODO: convert to univariate polynomial } impl GaloisField for AlgebraicExtension> @@ -62,7 +62,11 @@ where Self::Base: PolynomialGCD, ::Element: Copy, { - AlgebraicExtension::galois_field(self.poly.field.clone(), new_pow) + AlgebraicExtension::galois_field( + self.poly.field.clone(), + new_pow, + self.poly.variables[0].clone(), + ) } fn upgrade_element( @@ -155,12 +159,11 @@ where { /// Construct the Galois field GF(prime^exp). /// The irreducible polynomial is determined automatically. - pub fn galois_field(prime: FiniteField, exp: usize) -> Self { + pub fn galois_field(prime: FiniteField, exp: usize, var: Variable) -> Self { assert!(exp > 0); if exp == 1 { - let mut poly = - MultivariatePolynomial::new(&prime, None, Arc::new(vec![Variable::Temporary(0)])); + let mut poly = MultivariatePolynomial::new(&prime, None, Arc::new(vec![var])); poly.append_monomial(prime.one(), &[1]); return AlgebraicExtension::new(poly); @@ -185,11 +188,7 @@ where let mut coeffs = vec![0; exp as usize + 1]; coeffs[exp as usize] = 1; - let mut poly = MultivariatePolynomial::new( - &prime, - Some(coeffs.len()), - Arc::new(vec![Variable::Temporary(0)]), - ); + let mut poly = MultivariatePolynomial::new(&prime, Some(coeffs.len()), Arc::new(vec![var])); // find the minimal polynomial let p = prime.get_prime().to_integer(); @@ -265,7 +264,7 @@ impl AlgebraicExtension { pub fn new(poly: MultivariatePolynomial) -> AlgebraicExtension { if poly.nvars() == 1 { return AlgebraicExtension { - poly: Rc::new(poly), + poly: Arc::new(poly), }; } @@ -274,7 +273,7 @@ impl AlgebraicExtension { let uni = poly.to_univariate_from_univariate(v); AlgebraicExtension { - poly: Rc::new(uni.to_multivariate()), + poly: Arc::new(uni.to_multivariate()), } } @@ -298,7 +297,7 @@ impl AlgebraicExtension { FiniteField: FiniteFieldCore, { AlgebraicExtension { - poly: Rc::new( + poly: Arc::new( self.poly .map_coeff(|c| c.to_finite_field(field), field.clone()), ), @@ -663,6 +662,7 @@ mod tests { use crate::domains::algebraic_number::AlgebraicExtension; use crate::domains::finite_field::{PrimeIteratorU64, Zp, Z2}; use crate::domains::rational::Q; + use crate::state::State; #[test] fn gcd_number_field() -> Result<(), String> { @@ -688,12 +688,16 @@ mod tests { #[test] fn galois() { for j in 1..10 { - let _ = AlgebraicExtension::galois_field(Z2, j); + let _ = AlgebraicExtension::galois_field(Z2, j, State::get_symbol("v1").into()); } for i in PrimeIteratorU64::new(2).take(20) { for j in 1..10 { - let _ = AlgebraicExtension::galois_field(Zp::new(i as u32), j); + let _ = AlgebraicExtension::galois_field( + Zp::new(i as u32), + j, + State::get_symbol("v1").into(), + ); } } } diff --git a/src/domains/finite_field.rs b/src/domains/finite_field.rs index 4238c7d..960a707 100644 --- a/src/domains/finite_field.rs +++ b/src/domains/finite_field.rs @@ -5,6 +5,7 @@ use std::ops::{Deref, Neg}; use crate::domains::integer::Integer; use crate::poly::gcd::PolynomialGCD; +use crate::poly::Variable::Temporary; use crate::printer::PrintOptions; use super::algebraic_number::AlgebraicExtension; @@ -121,7 +122,7 @@ where Self::Base: PolynomialGCD, ::Element: Copy, { - AlgebraicExtension::galois_field(self.clone(), new_pow) + AlgebraicExtension::galois_field(self.clone(), new_pow, Temporary(0)) } fn upgrade_element( diff --git a/src/domains/rational.rs b/src/domains/rational.rs index 9e260ca..6b96c70 100644 --- a/src/domains/rational.rs +++ b/src/domains/rational.rs @@ -12,7 +12,9 @@ use rug::{ use crate::{poly::gcd::LARGE_U32_PRIMES, printer::PrintOptions, utils}; use super::{ - finite_field::{FiniteField, FiniteFieldCore, FiniteFieldWorkspace, ToFiniteField, Zp}, + finite_field::{ + FiniteField, FiniteFieldCore, FiniteFieldWorkspace, ToFiniteField, Two, Zp, Z2, + }, integer::{Integer, Z}, EuclideanDomain, Field, Ring, }; @@ -176,6 +178,26 @@ impl ToFiniteField for Rational { } } +impl ToFiniteField for Rational { + fn to_finite_field(&self, field: &Z2) -> ::Element { + match self { + &Rational::Natural(n, d) => { + let mut ff = n.rem_euclid(2) as u8; + + if d != 1 { + let df = n.rem_euclid(2) as u8; + field.div_assign(&mut ff, &df); + } + + ff + } + Rational::Large(r) => { + field.div(&(r.numer().mod_u(2) as u8), &(r.denom().mod_u(2) as u8)) + } + } + } +} + impl Rational { pub fn new(mut num: i64, mut den: i64) -> Rational { if den == 0 { diff --git a/src/poly.rs b/src/poly.rs index 0f344ab..ce5506e 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -1189,6 +1189,76 @@ impl MultivariatePolynomial { out.as_view().normalize(workspace, &mut norm); std::mem::swap(norm.deref_mut(), out); } + + pub fn to_expression_with_coeff_map(&self, f: F) -> Atom { + let mut out = Atom::default(); + self.to_expression_with_coeff_map_into(f, &mut out); + out + } + + pub fn to_expression_with_coeff_map_into( + &self, + f: F, + out: &mut Atom, + ) { + Workspace::get_local().with(|ws| self.to_expression_coeff_map_impl(ws, f, out)); + } + + pub(crate) fn to_expression_coeff_map_impl( + &self, + workspace: &Workspace, + f: F, + out: &mut Atom, + ) { + if self.is_zero() { + out.set_from_view(&workspace.new_num(0).as_view()); + return; + } + + let add = out.to_add(); + + let mut mul_h = workspace.new_atom(); + let mut var_h = workspace.new_atom(); + let mut num_h = workspace.new_atom(); + let mut pow_h = workspace.new_atom(); + + let mut coeff = workspace.new_atom(); + for monomial in self { + let mul = mul_h.to_mul(); + + for (var_id, &pow) in self.variables.iter().zip(monomial.exponents) { + if pow > E::zero() { + match var_id { + Variable::Symbol(v) => { + var_h.to_var(*v); + } + Variable::Temporary(_) => { + unreachable!("Temporary variables not supported"); + } + Variable::Function(_, a) | Variable::Other(a) => { + var_h.set_from_view(&a.as_view()); + } + } + + if pow > E::one() { + num_h.to_num((pow.to_u32() as i64).into()); + pow_h.to_pow(var_h.as_view(), num_h.as_view()); + mul.extend(pow_h.as_view()); + } else { + mul.extend(var_h.as_view()); + } + } + } + + f(&self.field, &monomial.coefficient, &mut coeff); + mul.extend(coeff.as_view()); + add.extend(mul_h.as_view()); + } + + let mut norm = workspace.new_atom(); + out.as_view().normalize(workspace, &mut norm); + std::mem::swap(norm.deref_mut(), out); + } } impl RationalPolynomial { diff --git a/src/poly/factor.rs b/src/poly/factor.rs index 1cd32c5..f6f595c 100644 --- a/src/poly/factor.rs +++ b/src/poly/factor.rs @@ -442,6 +442,11 @@ impl Factorize let mut full_factors = vec![]; for (f, p) in &sf { + if f.is_constant() { + full_factors.push((f.clone(), *p)); + continue; + } + let (v, s, g, n) = f.norm_impl(); let factors = n.factor(); @@ -811,12 +816,16 @@ where let mut exp = vec![E::zero(); self.nvars()]; let mut try_counter = 0; + let characteristic = self.field.characteristic(); let factor = loop { // generate a random non-constant polynomial random_poly.clear(); - if d == 1 { + if d == 1 + && (characteristic.is_zero() + || &Integer::from(try_counter as i64) < &characteristic) + { exp[var] = E::zero(); random_poly.append_monomial(self.field.nth(try_counter), &exp); exp[var] = E::one(); @@ -826,7 +835,7 @@ where for i in 0..2 * d { let r = self .field - .sample(&mut rng, (0, self.field.characteristic().to_u64() as i64)); + .sample(&mut rng, (0, characteristic.to_i64().unwrap_or(i64::MAX))); if !F::is_zero(&r) { exp[var] = E::from_u32(i as u32); random_poly.append_monomial(r, &exp); diff --git a/src/poly/groebner.rs b/src/poly/groebner.rs index 47129c0..2533a99 100644 --- a/src/poly/groebner.rs +++ b/src/poly/groebner.rs @@ -3,7 +3,8 @@ use std::{cmp::Ordering, rc::Rc}; use ahash::HashMap; use crate::domains::{ - finite_field::{FiniteField, FiniteFieldCore, Mersenne64, Zp, Zp64}, + algebraic_number::AlgebraicExtension, + finite_field::{FiniteField, FiniteFieldCore, Mersenne64, Zp, Zp64, Z2}, rational::RationalField, Field, Ring, }; @@ -925,6 +926,10 @@ macro_rules! echelonize_impl { echelonize_impl!(Zp64); echelonize_impl!(FiniteField); echelonize_impl!(RationalField); +echelonize_impl!(Z2); +echelonize_impl!(AlgebraicExtension); +echelonize_impl!(AlgebraicExtension); +echelonize_impl!(AlgebraicExtension); #[cfg(test)] mod test { diff --git a/symbolica.pyi b/symbolica.pyi index 0e796d4..2f06100 100644 --- a/symbolica.pyi +++ b/symbolica.pyi @@ -804,11 +804,39 @@ class Expression: (x+6)**-4 """ + @overload def to_polynomial(self, vars: Optional[Sequence[Expression]] = None) -> Polynomial: """Convert the expression to a polynomial, optionally, with the variable ordering specified in `vars`. All non-polynomial parts will be converted to new, independent variables. """ + @overload + def to_polynomial(self, minimal_poly: Expression, vars: Optional[Sequence[Expression]] = None, + ) -> NumberFieldPolynomial: + """Convert the expression to a polynomial, optionally, with the variables and the ordering specified in `vars`. + All non-polynomial elements will be converted to new independent variables. + + The coefficients will be converted to a number field with the minimal polynomial `minimal_poly`. + The minimal polynomial must be a monic, irreducible univariate polynomial. + """ + + @overload + def to_polynomial(self, + modulus: int, + power: Optional[Tuple[int, Expression]] = None, + minimal_poly: Optional[Expression] = None, + vars: Optional[Sequence[Expression]] = None, + ) -> FiniteFieldPolynomial: + """Convert the expression to a polynomial, optionally, with the variables and the ordering specified in `vars`. + All non-polynomial elements will be converted to new independent variables. + + The coefficients will be converted to finite field elements modulo `modulus`. + If on top an `extension` is provided, for example `(2, a)`, the polynomial will be converted to the Galois field + `GF(modulus^2)` where `a` is the variable of the minimal polynomial of the field. + + If a `minimal_poly` is provided, the Galois field will be created with `minimal_poly` as the minimal polynomial. + """ + def to_rational_polynomial( self, vars: Optional[Sequence[Expression]] = None, @@ -2058,6 +2086,186 @@ class IntegerPolynomial: """ +class NumberFieldPolynomial: + """A Symbolica polynomial with rational coefficients.""" + + def __copy__(self) -> NumberFieldPolynomial: + """Copy the polynomial.""" + + def __str__(self) -> str: + """Print the polynomial in a human-readable format.""" + + def to_latex(self) -> str: + """Convert the polynomial into a LaTeX string.""" + + def pretty_str( + self, + terms_on_new_line: bool = False, + color_top_level_sum: bool = True, + color_builtin_symbols: bool = True, + print_finite_field: bool = True, + symmetric_representation_for_finite_field: bool = False, + explicit_rational_polynomial: bool = False, + number_thousands_separator: Optional[str] = None, + multiplication_operator: str = "*", + square_brackets_for_function: bool = False, + num_exp_as_superscript: bool = True, + latex: bool = False, + ) -> str: + """ + Convert the polynomial into a human-readable string, with tunable settings. + + Examples + -------- + >>> p = FiniteFieldNumberFieldPolynomial.parse("3*x^2+2*x+7*x^3", ['x'], 11) + >>> print(p.pretty_str(symmetric_representation_for_finite_field=True)) + + Yields `z³⁴+x^(x+2)+y⁴+f(x,x²)+128_378_127_123 z^(2/3) w² x⁻¹ y⁻¹+3/5`. + """ + + def nterms(self) -> int: + """Get the number of terms in the polynomial.""" + + def get_var_list(self) -> Sequence[Expression]: + """Get the list of variables in the internal ordering of the polynomial.""" + + def __add__(self, rhs: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Add two polynomials `self` and `rhs`, returning the result.""" + + def __sub__(self, rhs: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Subtract polynomials `rhs` from `self`, returning the result.""" + + def __mul__(self, rhs: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Multiply two polynomials `self` and `rhs`, returning the result.""" + + def __truediv__(self, rhs: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Divide the polynomial `self` by `rhs` if possible, returning the result.""" + + def quot_rem(self, rhs: NumberFieldPolynomial) -> Tuple[NumberFieldPolynomial, NumberFieldPolynomial]: + """Divide `self` by `rhs`, returning the quotient and remainder.""" + + def __mod__(self, rhs: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Compute the remainder of the division of `self` by `rhs`.""" + + def __neg__(self) -> NumberFieldPolynomial: + """Negate the polynomial.""" + + def gcd(self, rhs: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Compute the greatest common divisor (GCD) of two polynomials.""" + + def resultant(self, rhs: NumberFieldPolynomial, var: Expression) -> NumberFieldPolynomial: + """Compute the resultant of two polynomials with respect to the variable `var`.""" + + def factor_square_free(self) -> list[Tuple[NumberFieldPolynomial, int]]: + """Compute the square-free factorization of the polynomial. + + Examples + -------- + + >>> from symbolica import Expression + >>> p = Expression.parse('3*(2*x^2+y)(x^3+y)^2(1+4*y)^2(1+x)').expand().to_polynomial() + >>> print('Square-free factorization of {}:'.format(p)) + >>> for f, exp in p.factor_square_free(): + >>> print('\t({})^{}'.format(f, exp)) + """ + + def factor(self) -> list[Tuple[NumberFieldPolynomial, int]]: + """Factorize the polynomial. + + Examples + -------- + + >>> from symbolica import Expression + >>> p = Expression.parse('(x+1)(x+2)(x+3)(x+4)(x+5)(x^2+6)(x^3+7)(x+8)(x^4+9)(x^5+x+10)').expand().to_polynomial() + >>> print('Factorization of {}:'.format(p)) + >>> for f, exp in p.factor(): + >>> print('\t({})^{}'.format(f, exp)) + """ + + def derivative(self, x: Expression) -> NumberFieldPolynomial: + """Take a derivative in `x`. + + Examples + -------- + + >>> from symbolica import Expression + >>> x = Expression.symbol('x') + >>> p = Expression.parse('x^2+2').to_polynomial() + >>> print(p.derivative(x)) + """ + + def integrate(self, x: Expression) -> NumberFieldPolynomial: + """Integrate the polynomial in `x`. + + Examples + -------- + + >>> from symbolica import Expression + >>> x = Expression.symbol('x') + >>> p = Expression.parse('x^2+2').to_polynomial() + >>> print(p.integrate(x)) + """ + + def content(self) -> NumberFieldPolynomial: + """Get the content, i.e., the GCD of the coefficients. + + Examples + -------- + + >>> from symbolica import Expression + >>> p = Expression.parse('3x^2+6x+9').to_polynomial() + >>> print(p.content()) + """ + + def coefficient_list(self, xs: Optional[Expression | Sequence[Expression]]) -> list[Tuple[list[int], NumberFieldPolynomial]]: + """Get the coefficient list, optionally in the variables `xs`. + + Examples + -------- + + >>> from symbolica import Expression + >>> x = Expression.symbol('x') + >>> p = Expression.parse('x*y+2*x+x^2').to_polynomial() + >>> for n, pp in p.coefficient_list(x): + >>> print(n, pp) + """ + + @classmethod + def groebner_basis(_cls, system: list[NumberFieldPolynomial], grevlex: bool = True, print_stats: bool = False) -> list[NumberFieldPolynomial]: + """Compute the Groebner basis of a polynomial system. + + If `grevlex=True`, reverse graded lexicographical ordering is used, + otherwise the ordering is lexicographical. + + If `print_stats=True` intermediate statistics will be printed. + """ + + def to_expression(self) -> Expression: + """ Convert the polynomial to an expression. + + Examples + -------- + + >>> from symbolica import Expression + >>> e = Expression.parse('x*y+2*x+x^2') + >>> p = e.to_polynomial() + >>> print((e - p.to_expression()).expand()) + """ + + def replace(self, x: Expression, v: NumberFieldPolynomial) -> NumberFieldPolynomial: + """Replace the variable `x` with a polynomial `v`. + + Examples + -------- + + >>> from symbolica import Expression + >>> x = Expression.symbol('x') + >>> p = Expression.parse('x*y+2*x+x^2').to_polynomial() + >>> r = Expression.parse('y+1').to_polynomial()) + >>> p.replace(x, r) + """ + + class FiniteFieldPolynomial: """A Symbolica polynomial with finite field coefficients.""" @@ -2148,14 +2356,6 @@ class FiniteFieldPolynomial: def resultant(self, rhs: FiniteFieldPolynomial, var: Expression) -> FiniteFieldPolynomial: """Compute the resultant of two polynomials with respect to the variable `var`.""" - def optimize(self, iterations: int = 1000, to_file: str | None = None) -> Evaluator: - """ - Optimize the polynomial for evaluation using `iterations` number of iterations. - The optimized output can be exported in a C++ format using `to_file`. - - Returns an evaluator for the polynomial. - """ - def factor_square_free(self) -> list[Tuple[FiniteFieldPolynomial, int]]: """Compute the square-free factorization of the polynomial. @@ -2268,6 +2468,9 @@ class FiniteFieldPolynomial: >>> p.replace(x, r) """ + def to_expression(self) -> Expression: + """ Convert the polynomial to an expression.""" + class RationalPolynomial: """A Symbolica rational polynomial."""