#![cfg(feature = "flint")]
use std::ffi::{CStr, CString};
use std::mem::MaybeUninit;
use flint3_sys::{
c_char, c_void, fmpz, fmpz_poly_add, fmpz_poly_clear, fmpz_poly_init, fmpz_poly_mul,
fmpz_poly_scalar_mul_fmpz, fmpz_poly_set_coeff_fmpz, fmpz_poly_sub, fmpz_poly_t,
};
use crate::dense::DenseUnivariatePolynomial;
#[cfg(not(feature = "gmp"))]
use num_bigint::BigInt;
use ocas_domain::{Integer, IntegerDomain};
pub struct FlintUnivariatePolynomial {
raw: fmpz_poly_t,
domain: IntegerDomain,
}
impl std::fmt::Debug for FlintUnivariatePolynomial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FlintUnivariatePolynomial")
.field("domain", &self.domain)
.finish_non_exhaustive()
}
}
impl FlintUnivariatePolynomial {
pub fn new() -> Self {
let mut raw: fmpz_poly_t = unsafe { std::mem::zeroed() };
unsafe {
fmpz_poly_init(raw.as_mut_ptr());
Self {
raw,
domain: IntegerDomain,
}
}
}
pub fn from_dense(poly: &DenseUnivariatePolynomial<IntegerDomain>) -> Self {
let mut result = Self::new();
for (i, coeff) in poly.coeffs().iter().enumerate() {
let mut c = fmpz_from_integer(coeff);
unsafe {
fmpz_poly_set_coeff_fmpz(result.raw.as_mut_ptr(), i as i64, &c);
flint3_sys::fmpz_clear(&mut c);
}
}
result
}
pub fn to_dense(&self) -> DenseUnivariatePolynomial<IntegerDomain> {
let degree = unsafe { flint3_sys::fmpz_poly_degree(self.raw.as_ptr()) };
let mut coeffs = Vec::with_capacity(if degree < 0 { 0 } else { degree as usize + 1 });
for i in 0..=degree {
let mut c = MaybeUninit::<fmpz>::uninit();
unsafe {
flint3_sys::fmpz_poly_get_coeff_fmpz(c.as_mut_ptr(), self.raw.as_ptr(), i);
let mut c = c.assume_init();
coeffs.push(integer_from_fmpz(&c));
flint3_sys::fmpz_clear(&mut c);
}
}
DenseUnivariatePolynomial::from_coeffs(IntegerDomain, coeffs)
}
pub fn add(&self, other: &Self) -> Self {
let mut result = Self::new();
unsafe {
fmpz_poly_add(
result.raw.as_mut_ptr(),
self.raw.as_ptr(),
other.raw.as_ptr(),
);
}
result
}
pub fn sub(&self, other: &Self) -> Self {
let mut result = Self::new();
unsafe {
fmpz_poly_sub(
result.raw.as_mut_ptr(),
self.raw.as_ptr(),
other.raw.as_ptr(),
);
}
result
}
pub fn mul(&self, other: &Self) -> Self {
let mut result = Self::new();
unsafe {
fmpz_poly_mul(
result.raw.as_mut_ptr(),
self.raw.as_ptr(),
other.raw.as_ptr(),
);
}
result
}
pub fn mul_scalar(&self, scalar: &Integer) -> Self {
let mut result = Self::new();
let mut c = fmpz_from_integer(scalar);
unsafe {
fmpz_poly_scalar_mul_fmpz(result.raw.as_mut_ptr(), self.raw.as_ptr(), &c);
flint3_sys::fmpz_clear(&mut c);
}
result
}
}
impl Drop for FlintUnivariatePolynomial {
fn drop(&mut self) {
unsafe {
fmpz_poly_clear(self.raw.as_mut_ptr());
}
}
}
fn fmpz_from_integer(i: &Integer) -> fmpz {
let s = i.to_string();
let c_str = CString::new(s).expect("BigInt decimal string contains null byte");
unsafe {
let mut z = MaybeUninit::<fmpz>::uninit();
flint3_sys::fmpz_init(z.as_mut_ptr());
let _ = flint3_sys::fmpz_set_str(z.as_mut_ptr(), c_str.as_ptr() as *const c_char, 10);
z.assume_init()
}
}
fn integer_from_fmpz(z: &fmpz) -> Integer {
unsafe {
let c_str = flint3_sys::fmpz_get_str(std::ptr::null_mut(), 10, z);
let bytes = CStr::from_ptr(c_str).to_bytes();
let s = std::str::from_utf8(bytes).expect("FLINT produced a valid decimal integer string");
#[cfg(not(feature = "gmp"))]
let value =
BigInt::parse_bytes(bytes, 10).expect("FLINT produced a valid decimal integer string");
#[cfg(feature = "gmp")]
let value = rug::Integer::from_str_radix(s, 10)
.expect("FLINT produced a valid decimal integer string");
flint3_sys::flint_free(c_str as *mut c_void);
Integer::new(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use ocas_domain::Integer;
#[test]
fn flint_integer_round_trip() {
fn check(value: i64) {
let i = Integer::from(value);
let mut z = fmpz_from_integer(&i);
let round = integer_from_fmpz(&z);
unsafe {
flint3_sys::fmpz_clear(&mut z);
}
assert_eq!(round, i);
}
check(0);
check(1);
check(-1);
check(42);
check(-42);
check(i64::MAX);
check(i64::MIN);
}
#[test]
fn flint_large_coefficient_round_trip() {
let big = Integer::from(BigInt::from(10).pow(50));
let mut z = fmpz_from_integer(&big);
let round = integer_from_fmpz(&z);
unsafe {
flint3_sys::fmpz_clear(&mut z);
}
assert_eq!(round, big);
}
#[test]
fn flint_add_matches_dense() {
let a = int_poly(&[1, 2]);
let b = int_poly(&[3, 0, 4]);
let fa = FlintUnivariatePolynomial::from_dense(&a);
let fb = FlintUnivariatePolynomial::from_dense(&b);
assert_eq!(fa.add(&fb).to_dense(), a.add(&b));
}
#[test]
fn flint_sub_matches_dense() {
let a = int_poly(&[1, 2]);
let b = int_poly(&[3, 0, 4]);
let fa = FlintUnivariatePolynomial::from_dense(&a);
let fb = FlintUnivariatePolynomial::from_dense(&b);
assert_eq!(fa.sub(&fb).to_dense(), a.sub(&b));
}
#[test]
fn flint_mul_matches_dense() {
let a = int_poly(&[1, 2]);
let b = int_poly(&[3, 0, 4]);
let fa = FlintUnivariatePolynomial::from_dense(&a);
let fb = FlintUnivariatePolynomial::from_dense(&b);
assert_eq!(fa.mul(&fb).to_dense(), a.mul(&b));
}
#[test]
fn flint_scalar_mul_matches_dense() {
let a = int_poly(&[1, 2, 3]);
let s = Integer::from(7);
let fa = FlintUnivariatePolynomial::from_dense(&a);
assert_eq!(fa.mul_scalar(&s).to_dense(), a.mul_scalar(&s));
}
fn int_poly(coeffs: &[i64]) -> DenseUnivariatePolynomial<IntegerDomain> {
DenseUnivariatePolynomial::from_coeffs(
IntegerDomain,
coeffs.iter().copied().map(Integer::from).collect(),
)
}
}