#[cfg(test)]
use std::ffi::{CStr, CString};
use std::fmt;
use std::sync::Arc;
use rug::{Integer, Rational};
use super::error::NumberFieldError;
use crate::flint::ffi;
use crate::flint::poly::FlintPoly;
use crate::flint::rational::FlintRational as Fq;
use crate::flint::FlintInteger;
const FAST_POW_BITS: u64 = 1 << 26;
pub const MAX_CYCLOTOMIC_POLYNOMIAL_DEGREE: u64 = 1 << 24;
pub const MAX_FIELD_DEGREE: usize = 1024;
struct FqPoly {
buf: Box<ffi::FmpqPolyBuf>,
}
impl FqPoly {
fn new() -> Self {
let mut buf = Box::new(ffi::FmpqPolyBuf([0u8; 64]));
crate::flint::note_thread_uses_flint();
unsafe { ffi::fmpq_poly_init(&mut *buf) };
Self { buf }
}
fn from_integer_poly(p: &FlintPoly) -> Self {
let mut q = Self::new();
unsafe { ffi::fmpq_poly_set_fmpz_poly(&mut *q.buf, p.inner_ptr()) };
q
}
fn set_coeff(&mut self, i: usize, c: &Fq) {
unsafe { ffi::fmpq_poly_set_coeff_fmpq(&mut *self.buf, i as ffi::slong, c.as_ptr()) };
}
fn as_ptr(&self) -> *const ffi::FmpqPolyBuf {
&*self.buf
}
}
impl Drop for FqPoly {
fn drop(&mut self) {
unsafe { ffi::fmpq_poly_clear(&mut *self.buf) };
}
}
const SENTINEL: u8 = 0xAA;
struct NfInner {
nf: Box<ffi::NfBuf>,
defining: Vec<Integer>,
degree: usize,
disc: Integer,
var: String,
cyclotomic_order: Option<u64>,
}
unsafe impl Send for NfInner {}
unsafe impl Sync for NfInner {}
impl Drop for NfInner {
fn drop(&mut self) {
unsafe { ffi::nf_clear(&mut *self.nf) };
}
}
#[derive(Clone)]
pub struct NumberField {
inner: Arc<NfInner>,
}
impl NumberField {
pub fn new(coeffs: &[Rational]) -> Result<Self, NumberFieldError> {
Self::build(coeffs, None, true)
}
pub fn from_integer_coeffs(coeffs: &[Integer]) -> Result<Self, NumberFieldError> {
let rats: Vec<Rational> = coeffs.iter().map(Rational::from).collect();
Self::new(&rats)
}
pub fn from_strings(coeffs: &[String]) -> Result<Self, NumberFieldError> {
let mut rats = Vec::with_capacity(coeffs.len());
for c in coeffs {
rats.push(parse_rational(c)?);
}
Self::new(&rats)
}
pub fn cyclotomic(n: u64) -> Result<Self, NumberFieldError> {
if n == 0 {
return Err(NumberFieldError::CyclotomicOrderOutOfRange {
n,
max: MAX_FIELD_DEGREE,
});
}
let phi = euler_phi_u64(n);
if phi > MAX_FIELD_DEGREE as u64 {
return Err(NumberFieldError::CyclotomicOrderOutOfRange {
n,
max: MAX_FIELD_DEGREE,
});
}
let coeffs: Vec<Rational> = cyclotomic_polynomial(n)
.iter()
.map(Rational::from)
.collect();
Self::build(&coeffs, Some(n), false)
}
fn build(
coeffs: &[Rational],
cyclotomic_order: Option<u64>,
check_irreducible: bool,
) -> Result<Self, NumberFieldError> {
let integral = canonical_integral_poly(coeffs)?;
let degree = integral.len() - 1;
if !(1..=MAX_FIELD_DEGREE).contains(°ree) {
return Err(NumberFieldError::DegreeOutOfRange {
degree: degree as i64,
max: MAX_FIELD_DEGREE,
});
}
let poly = FlintPoly::from_rug_coefficients(&integral);
if check_irreducible {
if let Some(factor) = proper_factor(&poly) {
return Err(NumberFieldError::Reducible {
poly: render_integer_poly(&integral, "x"),
factor,
});
}
}
let disc = poly.discriminant().to_rug();
let fq_poly = FqPoly::from_integer_poly(&poly);
let mut nf = Box::new(ffi::NfBuf([SENTINEL; 512]));
crate::flint::note_thread_uses_flint();
unsafe { ffi::nf_init(&mut *nf, fq_poly.as_ptr()) };
Ok(Self {
inner: Arc::new(NfInner {
nf,
defining: integral,
degree,
disc,
var: "a".to_string(),
cyclotomic_order,
}),
})
}
#[must_use]
pub fn with_variable(&self, var: &str) -> Self {
let inner = &*self.inner;
let coeffs: Vec<Rational> = inner.defining.iter().map(Rational::from).collect();
let mut rebuilt = Self::build(&coeffs, inner.cyclotomic_order, false)
.expect("a field that exists can be rebuilt from its own polynomial");
Arc::get_mut(&mut rebuilt.inner)
.expect("freshly built, not yet shared")
.var = var.to_string();
rebuilt
}
pub fn degree(&self) -> usize {
self.inner.degree
}
pub fn defining_polynomial(&self) -> &[Integer] {
&self.inner.defining
}
pub fn defining_polynomial_string(&self, var: &str) -> String {
render_integer_poly(&self.inner.defining, var)
}
pub fn polynomial_discriminant(&self) -> Integer {
self.inner.disc.clone()
}
pub fn cyclotomic_order(&self) -> Option<u64> {
self.inner.cyclotomic_order
}
pub fn variable(&self) -> &str {
&self.inner.var
}
pub fn generator(&self) -> NumberFieldElement {
let mut e = NumberFieldElement::blank(self);
unsafe { ffi::nf_elem_gen(e.as_mut_ptr(), self.nf_ptr()) };
e
}
pub fn zero(&self) -> NumberFieldElement {
let mut e = NumberFieldElement::blank(self);
unsafe { ffi::nf_elem_zero(e.as_mut_ptr(), self.nf_ptr()) };
e
}
pub fn one(&self) -> NumberFieldElement {
let mut e = NumberFieldElement::blank(self);
unsafe { ffi::nf_elem_one(e.as_mut_ptr(), self.nf_ptr()) };
e
}
pub fn rational(&self, r: &Rational) -> NumberFieldElement {
self.element(std::slice::from_ref(r))
.expect("one coefficient always fits a field of degree >= 1")
}
pub fn element(&self, coeffs: &[Rational]) -> Result<NumberFieldElement, NumberFieldError> {
if coeffs.len() > self.degree() {
return Err(NumberFieldError::CoefficientCountMismatch {
got: coeffs.len(),
degree: self.degree(),
});
}
let mut poly = FqPoly::new();
for (i, c) in coeffs.iter().enumerate() {
poly.set_coeff(i, &Fq::from_rug(c));
}
let mut e = NumberFieldElement::blank(self);
unsafe { ffi::nf_elem_set_fmpq_poly(e.as_mut_ptr(), poly.as_ptr(), self.nf_ptr()) };
Ok(e)
}
pub fn element_from_strings(
&self,
coeffs: &[String],
) -> Result<NumberFieldElement, NumberFieldError> {
let mut rats = Vec::with_capacity(coeffs.len());
for c in coeffs {
rats.push(parse_rational(c)?);
}
self.element(&rats)
}
fn nf_ptr(&self) -> *const ffi::NfBuf {
&*self.inner.nf
}
#[cfg(test)]
pub(crate) fn untouched_tail(&self) -> usize {
self.inner
.nf
.0
.iter()
.rev()
.take_while(|&&b| b == SENTINEL)
.count()
}
}
impl PartialEq for NumberField {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner) || self.inner.defining == other.inner.defining
}
}
impl Eq for NumberField {}
impl fmt::Display for NumberField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Q[x]/({})",
render_integer_poly(&self.inner.defining, "x")
)
}
}
impl fmt::Debug for NumberField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NumberField({}, degree {})", self, self.inner.degree)
}
}
pub struct NumberFieldElement {
field: NumberField,
buf: Box<ffi::NfElemBuf>,
}
unsafe impl Send for NumberFieldElement {}
unsafe impl Sync for NumberFieldElement {}
impl NumberFieldElement {
fn blank(field: &NumberField) -> Self {
let mut buf = Box::new(ffi::NfElemBuf([SENTINEL; 128]));
crate::flint::note_thread_uses_flint();
unsafe { ffi::nf_elem_init(&mut *buf, field.nf_ptr()) };
Self {
field: field.clone(),
buf,
}
}
fn as_ptr(&self) -> *const ffi::NfElemBuf {
&*self.buf
}
fn as_mut_ptr(&mut self) -> *mut ffi::NfElemBuf {
&mut *self.buf
}
pub fn field(&self) -> &NumberField {
&self.field
}
fn same_field(&self, other: &Self) -> Result<(), NumberFieldError> {
if self.field == other.field {
Ok(())
} else {
Err(NumberFieldError::FieldMismatch {
lhs: self.field.defining_polynomial_string("x"),
rhs: other.field.defining_polynomial_string("x"),
})
}
}
fn max_coefficient_bits(&self) -> u64 {
self.coefficients()
.iter()
.map(|c| {
u64::from(c.numer().significant_bits()) + u64::from(c.denom().significant_bits())
})
.max()
.unwrap_or(0)
}
pub fn coefficients(&self) -> Vec<Rational> {
(0..self.field.degree())
.map(|i| {
let mut c = Fq::new();
unsafe {
ffi::nf_elem_get_coeff_fmpq(
c.as_mut_ptr(),
self.as_ptr(),
i as ffi::slong,
self.field.nf_ptr(),
);
}
c.to_rug()
})
.collect()
}
pub fn is_zero(&self) -> bool {
unsafe { ffi::nf_elem_is_zero(self.as_ptr(), self.field.nf_ptr()) != 0 }
}
pub fn is_one(&self) -> bool {
unsafe { ffi::nf_elem_is_one(self.as_ptr(), self.field.nf_ptr()) != 0 }
}
pub fn add(&self, other: &Self) -> Result<Self, NumberFieldError> {
self.binop(other, ffi::nf_elem_add)
}
pub fn sub(&self, other: &Self) -> Result<Self, NumberFieldError> {
self.binop(other, ffi::nf_elem_sub)
}
pub fn mul(&self, other: &Self) -> Result<Self, NumberFieldError> {
self.binop(other, ffi::nf_elem_mul)
}
pub fn div(&self, other: &Self) -> Result<Self, NumberFieldError> {
self.same_field(other)?;
if other.is_zero() {
return Err(NumberFieldError::NotInvertible);
}
self.binop(other, ffi::nf_elem_div)
}
fn binop(
&self,
other: &Self,
f: unsafe extern "C" fn(
*mut ffi::NfElemBuf,
*const ffi::NfElemBuf,
*const ffi::NfElemBuf,
*const ffi::NfBuf,
),
) -> Result<Self, NumberFieldError> {
self.same_field(other)?;
let mut out = Self::blank(&self.field);
unsafe {
f(
out.as_mut_ptr(),
self.as_ptr(),
other.as_ptr(),
self.field.nf_ptr(),
)
};
Ok(out)
}
#[must_use]
pub fn neg(&self) -> Self {
let mut out = Self::blank(&self.field);
unsafe { ffi::nf_elem_neg(out.as_mut_ptr(), self.as_ptr(), self.field.nf_ptr()) };
out
}
pub fn inverse(&self) -> Result<Self, NumberFieldError> {
if self.is_zero() {
return Err(NumberFieldError::NotInvertible);
}
let mut out = Self::blank(&self.field);
unsafe { ffi::nf_elem_inv(out.as_mut_ptr(), self.as_ptr(), self.field.nf_ptr()) };
Ok(out)
}
#[must_use]
pub fn pow(&self, exp: u64) -> Self {
self.try_pow(exp)
.unwrap_or_else(|t| panic!("NumberFieldElement::pow: {t}"))
}
pub fn try_pow(&self, exp: u64) -> Result<Self, crate::budget::BudgetTrip> {
if exp <= 1 || self.is_zero() || self.is_one() {
return Ok(self.pow_unchecked(exp));
}
let d = self.field.degree() as u64;
let slack = self
.field
.defining_polynomial()
.iter()
.map(|c| u64::from(c.significant_bits()))
.max()
.unwrap_or(0)
.saturating_add(1)
.saturating_mul(d)
.saturating_add(64);
let base_bits = self.max_coefficient_bits().saturating_add(slack);
if exp.saturating_mul(base_bits).saturating_mul(d + 1) <= FAST_POW_BITS {
return Ok(self.pow_unchecked(exp));
}
let check = |x: &Self, y: &Self| {
let bits = x
.max_coefficient_bits()
.saturating_add(y.max_coefficient_bits())
.saturating_add(slack);
crate::budget::preflight_bignum_bits(bits, (d + 1).saturating_mul(4))
};
let mut result = self.field.one();
let mut base = self.clone();
let mut e = exp;
loop {
if e & 1 == 1 {
check(&result, &base)?;
result = result.mul_unchecked(&base);
}
e >>= 1;
if e == 0 {
return Ok(result);
}
check(&base, &base)?;
let before = base.max_coefficient_bits();
base = base.mul_unchecked(&base);
let after = base.max_coefficient_bits();
if after > before.saturating_add(slack) {
let projected = (after - before).saturating_mul(e);
crate::budget::preflight_bignum_bits(projected, (d + 1).saturating_mul(4))?;
}
}
}
fn mul_unchecked(&self, other: &Self) -> Self {
self.mul(other)
.expect("both operands come from the same field")
}
fn pow_unchecked(&self, exp: u64) -> Self {
let mut out = Self::blank(&self.field);
unsafe { ffi::nf_elem_pow(out.as_mut_ptr(), self.as_ptr(), exp, self.field.nf_ptr()) };
out
}
pub fn norm(&self) -> Rational {
let mut out = Fq::new();
unsafe { ffi::nf_elem_norm(out.as_mut_ptr(), self.as_ptr(), self.field.nf_ptr()) };
out.to_rug()
}
pub fn trace(&self) -> Rational {
let mut out = Fq::new();
unsafe { ffi::nf_elem_trace(out.as_mut_ptr(), self.as_ptr(), self.field.nf_ptr()) };
out.to_rug()
}
pub fn minimal_polynomial(&self) -> Vec<Rational> {
let d = self.field.degree();
let mut basis: Vec<(usize, Vec<Rational>, Vec<Rational>)> = Vec::new();
let mut power = self.field.one();
for k in 0..=d {
let mut w = power.coefficients();
let mut combo = vec![Rational::new(); k + 1];
combo[k] = Rational::from(1);
for (pivot, bv, bc) in &basis {
let factor = w[*pivot].clone();
if factor == 0 {
continue;
}
for (wi, bi) in w.iter_mut().zip(bv.iter()) {
*wi -= Rational::from(&factor * bi);
}
for (ci, bi) in combo.iter_mut().zip(bc.iter()) {
*ci -= Rational::from(&factor * bi);
}
}
match w.iter().position(|c| *c != 0) {
None => {
combo.truncate(k + 1);
return combo;
}
Some(pivot) => {
let lead = w[pivot].clone();
for wi in w.iter_mut() {
*wi /= &lead;
}
for ci in combo.iter_mut() {
*ci /= &lead;
}
combo.resize(d + 1, Rational::new());
basis.push((pivot, w, combo));
}
}
if k < d {
power = power.mul(self).expect("same field by construction");
}
}
unreachable!("the powers 1..a^d of a degree-d element are always dependent")
}
#[cfg(test)]
pub(crate) fn flint_string(&self) -> String {
let var = CString::new(self.field.variable()).expect("variable has no NUL");
unsafe {
let raw = ffi::nf_elem_get_str_pretty(self.as_ptr(), var.as_ptr(), self.field.nf_ptr());
let owned = CStr::from_ptr(raw).to_string_lossy().into_owned();
ffi::flint_free(raw.cast());
owned
}
}
#[cfg(test)]
pub(crate) fn untouched_tail(&self) -> usize {
self.buf
.0
.iter()
.rev()
.take_while(|&&b| b == SENTINEL)
.count()
}
}
impl Clone for NumberFieldElement {
fn clone(&self) -> Self {
let mut out = Self::blank(&self.field);
unsafe { ffi::nf_elem_set(out.as_mut_ptr(), self.as_ptr(), self.field.nf_ptr()) };
out
}
}
impl Drop for NumberFieldElement {
fn drop(&mut self) {
unsafe { ffi::nf_elem_clear(&mut *self.buf, self.field.nf_ptr()) };
}
}
impl PartialEq for NumberFieldElement {
fn eq(&self, other: &Self) -> bool {
self.field == other.field
&& unsafe {
ffi::nf_elem_equal(self.as_ptr(), other.as_ptr(), self.field.nf_ptr()) != 0
}
}
}
impl Eq for NumberFieldElement {}
impl fmt::Display for NumberFieldElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
render_rational_poly(&self.coefficients(), self.field.variable())
)
}
}
impl fmt::Debug for NumberFieldElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NumberFieldElement({} in {})", self, self.field)
}
}
pub fn cyclotomic_polynomial(n: u64) -> Vec<Integer> {
try_cyclotomic_polynomial(n).unwrap_or_else(|e| panic!("cyclotomic_polynomial: {e}"))
}
pub fn try_cyclotomic_polynomial(
n: u64,
) -> Result<Vec<Integer>, crate::number_theory::arith::ArithmeticError> {
if n == 0 {
return Ok(vec![Integer::from(1)]);
}
if euler_phi_u64(n) > MAX_CYCLOTOMIC_POLYNOMIAL_DEGREE {
return Err(
crate::number_theory::arith::ArithmeticError::WorkLimitExceeded {
function: "cyclotomic_polynomial (degree phi(n))",
limit: MAX_CYCLOTOMIC_POLYNOMIAL_DEGREE,
},
);
}
let p = FlintPoly::cyclotomic(n);
let len = p.length();
Ok((0..len).map(|i| p.get_coeff_flint(i).to_rug()).collect())
}
fn euler_phi_u64(n: u64) -> u64 {
let z = FlintInteger::from_rug(&Integer::from(n));
let mut out = FlintInteger::new();
unsafe { ffi::fmpz_euler_phi(out.inner_mut_ptr(), z.inner_ptr()) };
out.to_rug().to_u64().unwrap_or(u64::MAX)
}
fn parse_rational(text: &str) -> Result<Rational, NumberFieldError> {
Rational::from_str_radix(text.trim(), 10).map_err(|_| NumberFieldError::MalformedCoefficient {
text: text.to_string(),
})
}
fn canonical_integral_poly(coeffs: &[Rational]) -> Result<Vec<Integer>, NumberFieldError> {
let last = coeffs.iter().rposition(|c| *c != 0);
let Some(last) = last else {
return Err(NumberFieldError::EmptyDefiningPolynomial);
};
let trimmed = &coeffs[..=last];
let mut lcm = Integer::from(1);
for c in trimmed {
lcm = lcm.lcm(c.denom());
}
let mut ints: Vec<Integer> = trimmed
.iter()
.map(|c| {
(Rational::from(c) * Rational::from(&lcm))
.into_numer_denom()
.0
})
.collect();
let mut content = Integer::new();
for c in &ints {
content = content.gcd(c);
}
if content == 0 {
return Err(NumberFieldError::EmptyDefiningPolynomial);
}
let negate = ints[ints.len() - 1] < 0;
for c in ints.iter_mut() {
*c /= &content;
if negate {
*c *= -1;
}
}
Ok(ints)
}
fn proper_factor(poly: &FlintPoly) -> Option<String> {
if poly.degree() <= 1 {
return None;
}
let (_, factors) = poly.factor_over_z().ok()?;
if factors.len() == 1 && factors[0].1 == 1 {
return None;
}
let first = &factors[0].0;
let coeffs: Vec<Integer> = (0..first.length())
.map(|i| first.get_coeff_flint(i).to_rug())
.collect();
Some(render_integer_poly(&coeffs, "x"))
}
fn render_integer_poly(coeffs: &[Integer], var: &str) -> String {
let rats: Vec<Rational> = coeffs.iter().map(Rational::from).collect();
render_rational_poly(&rats, var)
}
fn render_rational_poly(coeffs: &[Rational], var: &str) -> String {
let mut out = String::new();
for (i, c) in coeffs.iter().enumerate().rev() {
if *c == 0 {
continue;
}
let negative = *c < 0;
let mag = Rational::from(c.abs_ref());
if out.is_empty() {
if negative {
out.push('-');
}
} else if negative {
out.push_str(" - ");
} else {
out.push_str(" + ");
}
let unit = mag == 1;
if !unit || i == 0 {
out.push_str(&mag.to_string());
if i > 0 {
out.push('*');
}
}
if i == 1 {
out.push_str(var);
} else if i > 1 {
out.push_str(var);
out.push('^');
out.push_str(&i.to_string());
}
}
if out.is_empty() {
out.push('0');
}
out
}