use super::ffi;
use std::ffi::CString;
use std::fmt;
use std::ops::{Add, Mul, Sub};
pub(crate) struct FlintPolyFactor {
inner: ffi::FmpzPolyFactorStruct,
}
impl FlintPolyFactor {
pub fn new() -> Self {
let mut inner = std::mem::MaybeUninit::<ffi::FmpzPolyFactorStruct>::uninit();
crate::flint::note_thread_uses_flint();
unsafe { ffi::fmpz_poly_factor_init(inner.as_mut_ptr()) };
Self {
inner: unsafe { inner.assume_init() },
}
}
pub fn factor(&mut self, poly: &FlintPoly) {
unsafe { ffi::fmpz_poly_factor(&mut self.inner, &poly.inner) };
}
pub fn len(&self) -> usize {
self.inner.num.max(0) as usize
}
pub fn unit(&self) -> super::integer::FlintInteger {
let mut u = super::integer::FlintInteger::new();
unsafe { ffi::fmpz_poly_factor_get_fmpz(u.inner_mut_ptr(), &self.inner) };
u
}
pub fn poly_at(&self, i: usize) -> FlintPoly {
assert!(
i < self.len(),
"factor index {i} out of range (len {})",
self.len()
);
let mut p = FlintPoly::new();
unsafe { ffi::fmpz_poly_factor_get_fmpz_poly(&mut p.inner, &self.inner, i as ffi::slong) };
p
}
pub fn exp_at(&self, i: usize) -> u32 {
assert!(
i < self.len(),
"factor index {i} out of range (len {})",
self.len()
);
unsafe { *self.inner.exp.add(i) as u32 }
}
}
impl Drop for FlintPolyFactor {
fn drop(&mut self) {
unsafe { ffi::fmpz_poly_factor_clear(&mut self.inner) };
}
}
pub const MAX_COEFF_INDEX: usize = (isize::MAX as usize) / std::mem::size_of::<ffi::fmpz>() - 1;
#[track_caller]
pub(crate) fn check_coeff_index(n: usize) {
assert!(
n <= MAX_COEFF_INDEX,
"coefficient index {n} is past the largest addressable polynomial length"
);
}
pub struct FlintPoly {
inner: ffi::FmpzPolyStruct,
}
unsafe impl Send for FlintPoly {}
unsafe impl Sync for FlintPoly {}
impl FlintPoly {
pub fn new() -> Self {
let mut inner = ffi::FmpzPolyStruct {
coeffs: std::ptr::null_mut(),
alloc: 0,
length: 0,
};
crate::flint::note_thread_uses_flint();
unsafe { ffi::fmpz_poly_init(&mut inner) };
FlintPoly { inner }
}
pub fn from_coefficients(coeffs: &[i64]) -> Self {
let mut p = Self::new();
if let Some(last) = coeffs.len().checked_sub(1) {
check_coeff_index(last);
}
for (i, &c) in coeffs.iter().enumerate() {
unsafe { ffi::fmpz_poly_set_coeff_si(&mut p.inner, i as ffi::slong, c) };
}
p
}
pub fn length(&self) -> usize {
unsafe { ffi::fmpz_poly_length(&self.inner) as usize }
}
pub fn degree(&self) -> i64 {
unsafe { ffi::fmpz_poly_degree(&self.inner) }
}
pub fn get_coeff(&self, n: usize) -> i64 {
if n >= self.length() {
return 0;
}
unsafe { ffi::fmpz_poly_get_coeff_si(&self.inner, n as ffi::slong) }
}
pub fn coefficients(&self) -> Vec<i64> {
(0..self.length()).map(|i| self.get_coeff(i)).collect()
}
pub fn is_zero(&self) -> bool {
self.length() == 0
}
pub fn pow(&self, exp: u32) -> Self {
self.checked_pow(exp).unwrap_or_else(|| {
panic!(
"FlintPoly::pow: the result of raising a degree-{} polynomial to the \
power {exp} would not fit in memory (E-POLY-004)",
self.degree()
)
})
}
pub fn checked_pow(&self, exp: u32) -> Option<Self> {
let deg = self.degree();
if deg >= 0 && exp > 1 {
let coeffs: Vec<rug::Integer> = (0..self.length())
.map(|i| self.get_coeff_flint(i).to_rug())
.collect();
let nonzero = coeffs.iter().filter(|c| **c != 0).count();
let dense = (deg as f64) * f64::from(exp) + 1.0;
let terms = crate::poly::size::power_term_bound(nonzero, exp, dense);
let bits = f64::from(exp) * crate::poly::size::log2_l1_norm(&coeffs);
crate::poly::size::check_power_size(terms, bits, 8).ok()?;
crate::poly::size::check_power_size(dense, 0.0, 8).ok()?;
}
Some(self.pow_unchecked(exp))
}
fn pow_unchecked(&self, exp: u32) -> Self {
let deg = self.degree();
if deg >= 1 && exp > 1 {
if let Some(top) = (deg as usize).checked_mul(exp as usize) {
if (0..deg as usize).all(|i| self.get_coeff_flint(i).to_rug() == 0) {
let mut res = Self::new();
let c = self.leading_coeff_fmpz().pow(u64::from(exp));
res.set_coeff_flint(top, &c);
return res;
}
}
}
let mut res = Self::new();
unsafe { ffi::fmpz_poly_pow(&mut res.inner, &self.inner, exp as ffi::ulong) };
res
}
pub fn checked_div_exact(&self, divisor: &Self) -> Option<Self> {
(!divisor.is_zero()).then(|| self.div_exact(divisor))
}
pub fn gcd(&self, other: &Self) -> Self {
let mut res = Self::new();
unsafe { ffi::fmpz_poly_gcd(&mut res.inner, &self.inner, &other.inner) };
res
}
pub fn div_exact(&self, divisor: &Self) -> Self {
assert!(
!divisor.is_zero(),
"attempt to divide a FlintPoly by the zero polynomial"
);
let mut res = Self::new();
unsafe { ffi::fmpz_poly_div(&mut res.inner, &self.inner, &divisor.inner) };
res
}
pub fn neg(&self) -> Self {
let mut res = Self::new();
unsafe { ffi::fmpz_poly_neg(&mut res.inner, &self.inner) };
res
}
pub fn scalar_mul_fmpz(&self, c: &super::integer::FlintInteger) -> Self {
let mut res = Self::new();
unsafe { ffi::fmpz_poly_scalar_mul_fmpz(&mut res.inner, &self.inner, c.inner_ptr()) };
res
}
pub fn scalar_divexact_fmpz(&self, c: &super::integer::FlintInteger) -> Self {
assert!(!c.is_zero(), "attempt to divide a FlintPoly by zero");
let mut res = Self::new();
unsafe { ffi::fmpz_poly_scalar_divexact_fmpz(&mut res.inner, &self.inner, c.inner_ptr()) };
res
}
pub fn leading_coeff_fmpz(&self) -> super::integer::FlintInteger {
let deg = self.degree();
if deg < 0 {
return super::integer::FlintInteger::from_i64(0);
}
self.get_coeff_flint(deg as usize)
}
pub fn resultant(&self, other: &Self) -> super::integer::FlintInteger {
let mut res = super::integer::FlintInteger::new();
unsafe { ffi::fmpz_poly_resultant(res.inner_mut_ptr(), &self.inner, &other.inner) };
res
}
pub fn pseudo_divrem(&self, other: &Self) -> (Self, Self, u64) {
assert!(
!other.is_zero(),
"attempt to pseudo-divide a FlintPoly by the zero polynomial"
);
let mut q = Self::new();
let mut r = Self::new();
let mut d: ffi::ulong = 0;
unsafe {
ffi::fmpz_poly_pseudo_divrem(
&mut q.inner,
&mut r.inner,
&mut d,
&self.inner,
&other.inner,
)
};
(q, r, d)
}
pub fn set_coeff_flint(&mut self, n: usize, c: &super::integer::FlintInteger) {
check_coeff_index(n);
unsafe { ffi::fmpz_poly_set_coeff_fmpz(&mut self.inner, n as ffi::slong, c.inner_ptr()) };
}
pub fn get_coeff_flint(&self, n: usize) -> super::integer::FlintInteger {
let mut c = super::integer::FlintInteger::new();
if n >= self.length() {
return c;
}
unsafe { ffi::fmpz_poly_get_coeff_fmpz(c.inner_mut_ptr(), &self.inner, n as ffi::slong) };
c
}
pub fn discriminant(&self) -> super::integer::FlintInteger {
let mut res = super::integer::FlintInteger::new();
unsafe { ffi::fmpz_poly_discriminant(res.inner_mut_ptr(), &self.inner) };
res
}
pub(crate) fn inner_ptr(&self) -> *const ffi::FmpzPolyStruct {
&self.inner
}
pub fn derivative(&self) -> Self {
let mut result = Self::new();
unsafe { ffi::fmpz_poly_derivative(&mut result.inner, &self.inner) };
result
}
pub fn from_rug_coefficients(coeffs: &[rug::Integer]) -> Self {
let mut p = Self::new();
for (i, c) in coeffs.iter().enumerate() {
let fi = super::integer::FlintInteger::from_rug(c);
p.set_coeff_flint(i, &fi);
}
p
}
#[allow(clippy::result_unit_err)]
pub fn factor_over_z(
&self,
) -> Result<(super::integer::FlintInteger, Vec<(FlintPoly, u32)>), ()> {
let (unit, mut factors) = match crate::poly::factor::binomial::factor_binomial_z(self) {
Some(fast) => fast,
None => self.factor_over_z_flint_order()?,
};
factors.sort_by(crate::poly::factor::cmp_univariate_factors);
Ok((unit, factors))
}
#[allow(clippy::result_unit_err)]
pub(crate) fn factor_over_z_flint_order(
&self,
) -> Result<(super::integer::FlintInteger, Vec<(FlintPoly, u32)>), ()> {
if self.is_zero() {
return Err(());
}
let mut fac = FlintPolyFactor::new();
fac.factor(self);
let unit = fac.unit();
let factors = (0..fac.len())
.map(|i| (fac.poly_at(i), fac.exp_at(i)))
.collect();
Ok((unit, factors))
}
pub(crate) fn cmp_canonical(&self, other: &Self) -> std::cmp::Ordering {
let (la, lb) = (self.length(), other.length());
if la != lb {
return la.cmp(&lb);
}
for i in (0..la).rev() {
let c = unsafe { ffi::fmpz_cmp(self.inner.coeffs.add(i), other.inner.coeffs.add(i)) };
if c != 0 {
return c.cmp(&0);
}
}
std::cmp::Ordering::Equal
}
pub(crate) fn binomial_shape(&self) -> Option<(usize, usize)> {
let len = self.length();
if len < 2 {
return None;
}
let coeffs = unsafe { std::slice::from_raw_parts(self.inner.coeffs, len) };
let k = coeffs.iter().position(|&c| c != 0)?;
let top = len - 1;
if k == top || coeffs[k + 1..top].iter().any(|&c| c != 0) {
return None;
}
Some((k, top - k))
}
pub fn swinnerton_dyer(n: u64) -> Self {
let mut p = Self::new();
unsafe {
ffi::fmpz_poly_swinnerton_dyer(&mut p.inner, n as ffi::ulong);
}
p
}
pub fn cyclotomic(n: u64) -> Self {
let mut p = Self::new();
unsafe {
ffi::fmpz_poly_cyclotomic(&mut p.inner, n as ffi::ulong);
}
p
}
}
impl Default for FlintPoly {
fn default() -> Self {
Self::new()
}
}
impl Drop for FlintPoly {
fn drop(&mut self) {
unsafe { ffi::fmpz_poly_clear(&mut self.inner) };
}
}
impl Clone for FlintPoly {
fn clone(&self) -> Self {
let mut new = Self::new();
unsafe { ffi::fmpz_poly_set(&mut new.inner, &self.inner) };
new
}
}
impl PartialEq for FlintPoly {
fn eq(&self, other: &Self) -> bool {
unsafe { ffi::fmpz_poly_equal(&self.inner, &other.inner) != 0 }
}
}
impl Eq for FlintPoly {}
impl Add for FlintPoly {
type Output = Self;
fn add(self, rhs: Self) -> Self {
&self + &rhs
}
}
impl<'b> Add<&'b FlintPoly> for &FlintPoly {
type Output = FlintPoly;
fn add(self, rhs: &'b FlintPoly) -> FlintPoly {
let mut res = FlintPoly::new();
unsafe { ffi::fmpz_poly_add(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl Sub for FlintPoly {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
&self - &rhs
}
}
impl<'b> Sub<&'b FlintPoly> for &FlintPoly {
type Output = FlintPoly;
fn sub(self, rhs: &'b FlintPoly) -> FlintPoly {
let mut res = FlintPoly::new();
unsafe { ffi::fmpz_poly_sub(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl Mul for FlintPoly {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
&self * &rhs
}
}
impl<'b> Mul<&'b FlintPoly> for &FlintPoly {
type Output = FlintPoly;
fn mul(self, rhs: &'b FlintPoly) -> FlintPoly {
let mut res = FlintPoly::new();
unsafe { ffi::fmpz_poly_mul(&mut res.inner, &self.inner, &rhs.inner) };
res
}
}
impl fmt::Display for FlintPoly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
return write!(f, "0");
}
unsafe {
let var = CString::new("x").unwrap();
let ptr = ffi::fmpz_poly_get_str_pretty(&self.inner, var.as_ptr());
if ptr.is_null() {
return write!(f, "0");
}
let s = std::ffi::CStr::from_ptr(ptr)
.to_str()
.unwrap_or("<utf8-err>")
.to_owned();
ffi::flint_free(ptr as *mut std::ffi::c_void);
write!(f, "{}", s)
}
}
}
impl fmt::Debug for FlintPoly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FlintPoly({})", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_coeff_past_the_end_is_zero() {
let p = FlintPoly::from_coefficients(&[1, 2, 3]);
for n in [3, 4, 1 << 40, i64::MAX as usize, usize::MAX] {
assert_eq!(p.get_coeff(n), 0, "{n}");
assert!(p.get_coeff_flint(n).is_zero(), "{n}");
}
assert_eq!(p.get_coeff(2), 3);
assert_eq!(p.get_coeff_flint(1).to_i64(), 2);
}
#[test]
#[should_panic(expected = "past the largest addressable polynomial length")]
fn set_coeff_at_usize_max_panics_instead_of_corrupting_the_heap() {
let mut p = FlintPoly::from_coefficients(&[1, 2, 3]);
p.set_coeff_flint(
usize::MAX,
&super::super::integer::FlintInteger::from_i64(5),
);
}
#[test]
#[should_panic(expected = "past the largest addressable polynomial length")]
fn set_coeff_past_i64_max_panics() {
let mut p = FlintPoly::new();
p.set_coeff_flint(
i64::MAX as usize + 1,
&super::super::integer::FlintInteger::from_i64(5),
);
}
#[test]
#[should_panic(expected = "zero polynomial")]
fn div_exact_by_zero_panics_instead_of_aborting() {
let p = FlintPoly::from_coefficients(&[1, 2, 3]);
let _ = p.div_exact(&FlintPoly::new());
}
#[test]
#[should_panic(expected = "zero polynomial")]
fn pseudo_divrem_by_zero_panics_instead_of_aborting() {
let p = FlintPoly::from_coefficients(&[1, 2, 3]);
let _ = p.pseudo_divrem(&FlintPoly::new());
}
#[test]
#[should_panic(expected = "divide a FlintPoly by zero")]
fn scalar_divexact_by_zero_panics_instead_of_aborting() {
let p = FlintPoly::from_coefficients(&[2, 4]);
let _ = p.scalar_divexact_fmpz(&super::super::integer::FlintInteger::from_i64(0));
}
#[test]
fn checked_div_exact() {
let p = FlintPoly::from_coefficients(&[-1, 0, 1]);
let d = FlintPoly::from_coefficients(&[1, 1]);
assert!(p.checked_div_exact(&FlintPoly::new()).is_none());
assert_eq!(
p.checked_div_exact(&d).unwrap(),
FlintPoly::from_coefficients(&[-1, 1])
);
}
#[test]
fn nmod_get_coeff_negative_index_is_zero() {
let mut p = super::super::nmod::FlintNmodPoly::new(7);
p.set_coeff(0, 3);
p.set_coeff(1, 5);
assert_eq!(p.get_coeff(-1), 0);
assert_eq!(p.get_coeff(i64::MIN), 0);
assert_eq!(p.get_coeff(1), 5);
assert_eq!(p.get_coeff(9), 0);
}
#[test]
#[should_panic(expected = "past the largest addressable polynomial length")]
fn nmod_set_coeff_at_usize_max_panics() {
let mut p = super::super::nmod::FlintNmodPoly::new(7);
p.set_coeff(usize::MAX, 1);
}
#[test]
fn zero_poly() {
let p = FlintPoly::new();
assert!(p.is_zero());
assert_eq!(p.length(), 0);
assert_eq!(p.degree(), -1);
assert_eq!(p.coefficients(), Vec::<i64>::new());
}
#[test]
fn from_coefficients_roundtrip() {
let coeffs = vec![1i64, 2, 3];
let p = FlintPoly::from_coefficients(&coeffs);
assert_eq!(p.coefficients(), coeffs);
assert_eq!(p.degree(), 2);
assert_eq!(p.length(), 3);
}
#[test]
fn from_coefficients_zero_trailing() {
let p = FlintPoly::from_coefficients(&[1, 0]);
assert_eq!(p.degree(), 0);
assert_eq!(p.length(), 1);
}
#[test]
fn constant_poly() {
let p = FlintPoly::from_coefficients(&[42]);
assert_eq!(p.coefficients(), vec![42]);
assert_eq!(p.degree(), 0);
}
#[test]
fn equality() {
let a = FlintPoly::from_coefficients(&[1, 2, 3]);
let b = FlintPoly::from_coefficients(&[1, 2, 3]);
let c = FlintPoly::from_coefficients(&[1, 2, 4]);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn clone_is_independent() {
let a = FlintPoly::from_coefficients(&[1, 2, 3]);
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn add() {
let a = FlintPoly::from_coefficients(&[1, 2]);
let b = FlintPoly::from_coefficients(&[3, 4]);
let s = &a + &b;
assert_eq!(s.coefficients(), vec![4, 6]);
}
#[test]
fn sub() {
let a = FlintPoly::from_coefficients(&[5, 3]);
let b = FlintPoly::from_coefficients(&[2, 1]);
let d = &a - &b;
assert_eq!(d.coefficients(), vec![3, 2]);
}
#[test]
fn mul() {
let p = FlintPoly::from_coefficients(&[1, 1]);
let q = &p * &p;
assert_eq!(q.coefficients(), vec![1, 2, 1]);
}
#[test]
fn mul_non_trivial() {
let a = FlintPoly::from_coefficients(&[1, 2, 3]);
let b = FlintPoly::from_coefficients(&[4, 5]);
let c = &a * &b;
assert_eq!(c.coefficients(), vec![4, 13, 22, 15]);
}
#[test]
fn mul_by_zero() {
let a = FlintPoly::from_coefficients(&[1, 2, 3]);
let z = FlintPoly::new();
assert!((&a * &z).is_zero());
}
#[test]
fn pow_squared() {
let p = FlintPoly::from_coefficients(&[1, 1]);
let q = p.pow(2);
assert_eq!(q.coefficients(), vec![1, 2, 1]);
}
#[test]
fn pow_of_a_monomial_skips_the_binomial_expansion() {
let q = FlintPoly::from_coefficients(&[0, 0, -3]).pow(5);
let mut want = vec![0; 11];
want[10] = -243;
assert_eq!(q.coefficients(), want);
assert_eq!(
FlintPoly::from_coefficients(&[1, 1]).pow(3).coefficients(),
vec![1, 3, 3, 1]
);
let big = FlintPoly::from_coefficients(&[0, 1]).pow(1 << 21);
assert_eq!(big.degree(), 1 << 21);
assert_eq!(big.get_coeff(1 << 21), 1);
assert_eq!(big.get_coeff(0), 0);
}
#[test]
fn pow_zero() {
let p = FlintPoly::from_coefficients(&[1, 2, 3]);
let q = p.pow(0);
assert_eq!(q.coefficients(), vec![1]);
}
#[test]
fn pow_cubed() {
let p = FlintPoly::from_coefficients(&[1, 1]);
let q = p.pow(3);
assert_eq!(q.coefficients(), vec![1, 3, 3, 1]);
}
#[test]
fn gcd_trivial() {
let a = FlintPoly::from_coefficients(&[-1, 0, 1]); let b = FlintPoly::from_coefficients(&[-1, 1]); let g = a.gcd(&b);
assert_eq!(g.degree(), 1);
let coeffs = g.coefficients();
assert_eq!(coeffs[1].abs(), coeffs[0].abs());
assert_ne!(coeffs[0], 0);
}
#[test]
fn gcd_coprime() {
let a = FlintPoly::from_coefficients(&[0, 1]);
let b = FlintPoly::from_coefficients(&[1, 1]);
let g = a.gcd(&b);
assert_eq!(g.degree(), 0); }
#[test]
fn gcd_with_zero() {
let a = FlintPoly::from_coefficients(&[3, 2, 1]);
let z = FlintPoly::new();
let g = a.gcd(&z);
assert_eq!(g.degree(), a.degree());
}
#[test]
fn display_zero() {
assert_eq!(FlintPoly::new().to_string(), "0");
}
#[test]
fn display_constant() {
let p = FlintPoly::from_coefficients(&[5]);
assert_eq!(p.to_string(), "5");
}
#[test]
fn display_linear() {
let p = FlintPoly::from_coefficients(&[0, 1]);
assert_eq!(p.to_string(), "x");
}
#[test]
fn display_quadratic() {
let p = FlintPoly::from_coefficients(&[1, 2, 1]);
let s = p.to_string();
assert!(s.contains("x^2"), "unexpected display: {s}");
}
#[test]
fn coefficient_round_trip() {
let orig = vec![10i64, -5, 0, 3, 1];
let p = FlintPoly::from_coefficients(&orig);
assert_eq!(p.coefficients(), orig);
}
}