use rug::{Integer, Rational};
use super::divisor::{Divisor, Place};
use super::error::FunctionFieldError;
use super::field::FunctionField;
use super::util::{constant, deflate, eval, is_zero, rational_roots, rational_sqrt, valuation_at};
use crate::integrate::risch::poly_rde::{degree, poly_add, poly_mul, qpoly_to_expr, trim, QPoly};
use crate::integrate::risch::rational_rde::poly_sub;
use crate::kernel::{ExprId, ExprPool};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FunctionFieldElement {
field: FunctionField,
p: QPoly,
q: QPoly,
d: QPoly,
}
impl FunctionFieldElement {
pub fn new(
field: FunctionField,
p: QPoly,
q: QPoly,
d: QPoly,
) -> Result<Self, FunctionFieldError> {
let d = trim(d);
if is_zero(&d) {
return Err(FunctionFieldError::UnsupportedModel {
reason: "a function-field element cannot have a zero denominator".into(),
});
}
Ok(FunctionFieldElement {
field,
p: trim(p),
q: trim(q),
d,
})
}
pub fn polynomial(field: FunctionField, p: QPoly, q: QPoly) -> Self {
let d = field.one();
FunctionFieldElement {
field,
p: trim(p),
q: trim(q),
d,
}
}
pub fn constant(field: FunctionField, c: Rational) -> Self {
Self::polynomial(field, constant(c), Vec::new())
}
pub fn x(field: FunctionField) -> Self {
Self::polynomial(
field,
vec![Rational::from(0), Rational::from(1)],
Vec::new(),
)
}
pub fn y(field: FunctionField) -> Self {
let one = field.one();
Self::polynomial(field, Vec::new(), one)
}
pub fn field(&self) -> &FunctionField {
&self.field
}
pub fn numerator_rational_part(&self) -> &QPoly {
&self.p
}
pub fn numerator_algebraic_part(&self) -> &QPoly {
&self.q
}
pub fn denominator(&self) -> &QPoly {
&self.d
}
pub fn is_zero(&self) -> bool {
is_zero(&self.p) && is_zero(&self.q)
}
pub fn numerator_norm(&self) -> QPoly {
let p2 = poly_mul(&self.p, &self.p);
let q2a = poly_mul(&poly_mul(&self.q, &self.q), self.field.curve());
trim(poly_sub(&p2, &q2a))
}
pub fn mul(&self, other: &Self) -> Result<Self, FunctionFieldError> {
self.field.require_same(&other.field)?;
let a = self.field.curve();
let p = poly_add(
&poly_mul(&self.p, &other.p),
&poly_mul(&poly_mul(&self.q, &other.q), a),
);
let q = poly_add(&poly_mul(&self.p, &other.q), &poly_mul(&self.q, &other.p));
Ok(FunctionFieldElement {
field: self.field.clone(),
p: trim(p),
q: trim(q),
d: trim(poly_mul(&self.d, &other.d)),
})
}
pub fn divisor(&self) -> Result<Divisor, FunctionFieldError> {
self.field.require_imaginary("divisor_of_function")?;
if self.is_zero() {
return Err(FunctionFieldError::ZeroFunction);
}
let num = numerator_divisor(&self.field, &self.p, &self.q, "the numerator")?;
let den = numerator_divisor(&self.field, &self.d, &Vec::new(), "the denominator")?;
num.sub(&den)
}
pub fn to_expr(&self, var: ExprId, pool: &ExprPool) -> ExprId {
let p_e = qpoly_to_expr(&self.p, var, pool);
let num = if is_zero(&self.q) {
p_e
} else {
let root = pool.func("sqrt", vec![self.field.curve_expr(var, pool)]);
let q_e = qpoly_to_expr(&self.q, var, pool);
let term = pool.mul(vec![q_e, root]);
if is_zero(&self.p) {
term
} else {
pool.add(vec![p_e, term])
}
};
if degree(&self.d) == 0 && self.d[0] == 1 {
num
} else {
let d_e = qpoly_to_expr(&self.d, var, pool);
pool.mul(vec![num, pool.pow(d_e, pool.integer(-1_i32))])
}
}
}
fn numerator_divisor(
field: &FunctionField,
p: &QPoly,
q: &QPoly,
what: &'static str,
) -> Result<Divisor, FunctionFieldError> {
let a = field.curve();
let p = trim(p.clone());
let q = trim(q.clone());
if is_zero(&p) && is_zero(&q) {
return Err(FunctionFieldError::ZeroFunction);
}
let norm = trim(poly_sub(&poly_mul(&p, &p), &poly_mul(&poly_mul(&q, &q), a)));
if is_zero(&norm) {
return Err(FunctionFieldError::SelfCheckFailed {
detail: format!(
"the norm of {what} vanished, which cannot happen for a squarefree curve"
),
});
}
let roots = rational_roots(&norm).ok_or_else(|| FunctionFieldError::NonRationalSupport {
context: format!("{what} has a norm that does not split into rational linear factors"),
})?;
let mut terms: Vec<(Place, Integer)> = Vec::new();
let mut total = Integer::from(0);
for (alpha, mult) in roots {
let m = Integer::from(mult as u64);
let a_alpha = eval(a, &alpha);
if a_alpha == 0 {
terms.push((Place::finite(alpha, Rational::from(0)), m.clone()));
total += m;
continue;
}
let Some(beta) = rational_sqrt(&a_alpha) else {
return Err(FunctionFieldError::NonRationalSupport {
context: format!(
"{what} vanishes over x = {alpha}, where a(x) = {a_alpha} is not a rational \
square, so the place there has degree 2"
),
});
};
let k = match (valuation_at(&p, &alpha), valuation_at(&q, &alpha)) {
(Some(kp), Some(kq)) => kp.min(kq),
(Some(kp), None) => kp,
(None, Some(kq)) => kq,
(None, None) => unreachable!("p and q are not both zero"),
};
let p1 = deflate(&p, &alpha, k);
let q1 = deflate(&q, &alpha, k);
let at_plus = eval(&p1, &alpha) + Rational::from(&beta * &eval(&q1, &alpha));
let kk = Integer::from(k as u64);
let (v_plus, v_minus) = if at_plus != 0 {
(kk.clone(), m.clone() - kk)
} else {
(m.clone() - kk.clone(), kk)
};
if v_plus < 0 || v_minus < 0 {
return Err(FunctionFieldError::SelfCheckFailed {
detail: format!(
"splitting the norm multiplicity {m} at x = {alpha} produced a negative \
valuation ({v_plus}, {v_minus})"
),
});
}
terms.push((Place::finite(alpha.clone(), beta.clone()), v_plus));
terms.push((Place::finite(alpha, -beta), v_minus));
total += m;
}
let at_infinity = -total.clone();
let expected = infinity_valuation(field, &p, &q);
if at_infinity != expected {
return Err(FunctionFieldError::SelfCheckFailed {
detail: format!(
"v_∞ from degree balance is {at_infinity}, but the degrees of {what} give \
{expected}"
),
});
}
terms.push((Place::Infinity, at_infinity));
Divisor::from_terms(field.clone(), terms)
}
fn infinity_valuation(field: &FunctionField, p: &QPoly, q: &QPoly) -> i64 {
let da = field.curve_degree() as i64;
let from_p = if is_zero(p) { i64::MAX } else { -2 * degree(p) };
let from_q = if is_zero(q) {
i64::MAX
} else {
-da - 2 * degree(q)
};
from_p.min(from_q)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::AlkahestError;
fn qp(cs: &[i64]) -> QPoly {
trim(cs.iter().map(|&c| Rational::from(c)).collect())
}
fn e1() -> FunctionField {
FunctionField::hyperelliptic_from_i64(&[0, -1, 0, 1]).unwrap()
}
fn c2() -> FunctionField {
FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 0, 0, 1]).unwrap()
}
fn pl(x: i64, y: i64) -> Place {
Place::finite(Rational::from(x), Rational::from(y))
}
#[test]
fn divisor_of_x_on_a_branch_point() {
let f = e1();
let d = FunctionFieldElement::x(f).divisor().unwrap();
assert_eq!(d.coefficient(&pl(0, 0)), Integer::from(2));
assert_eq!(d.coefficient(&Place::Infinity), Integer::from(-2));
assert_eq!(d.degree(), Integer::from(0));
}
#[test]
fn divisor_of_y_is_the_branch_locus() {
let f = e1();
let d = FunctionFieldElement::y(f).divisor().unwrap();
for x in [-1i64, 0, 1] {
assert_eq!(d.coefficient(&pl(x, 0)), Integer::from(1));
}
assert_eq!(d.coefficient(&Place::Infinity), Integer::from(-3));
assert_eq!(d.degree(), Integer::from(0));
}
#[test]
fn divisor_of_x_at_a_non_branch_rational_fibre() {
let f = FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 1]).unwrap();
let u = FunctionFieldElement::polynomial(f, qp(&[-2, 1]), Vec::new());
let d = u.divisor().unwrap();
assert_eq!(d.coefficient(&pl(2, 3)), Integer::from(1));
assert_eq!(d.coefficient(&pl(2, -3)), Integer::from(1));
assert_eq!(d.coefficient(&Place::Infinity), Integer::from(-2));
}
#[test]
fn divisor_of_y_minus_beta_lands_on_one_sheet_only() {
let f = FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 1]).unwrap();
let u = FunctionFieldElement::polynomial(f, qp(&[-3]), qp(&[1]));
let err = u.divisor().unwrap_err();
assert_eq!(err.code(), "E-FFLD-003");
}
#[test]
fn divisor_of_y_minus_beta_when_the_norm_splits() {
let f = FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 1]).unwrap();
let u = FunctionFieldElement::polynomial(f, qp(&[-1]), qp(&[1]));
let d = u.divisor().unwrap();
assert_eq!(d.coefficient(&pl(0, 1)), Integer::from(3));
assert_eq!(d.coefficient(&pl(0, -1)), Integer::from(0));
assert_eq!(d.coefficient(&Place::Infinity), Integer::from(-3));
assert_eq!(d.degree(), Integer::from(0));
}
#[test]
fn divisor_of_a_quotient_subtracts() {
let f = e1();
let u = FunctionFieldElement::new(f, Vec::new(), qp(&[1]), qp(&[0, 1])).unwrap();
let d = u.divisor().unwrap();
assert_eq!(d.coefficient(&pl(-1, 0)), Integer::from(1));
assert_eq!(d.coefficient(&pl(0, 0)), Integer::from(-1));
assert_eq!(d.coefficient(&pl(1, 0)), Integer::from(1));
assert_eq!(d.coefficient(&Place::Infinity), Integer::from(-1));
assert_eq!(d.degree(), Integer::from(0));
}
#[test]
fn divisor_is_additive_under_multiplication() {
let f = e1();
let u = FunctionFieldElement::x(f.clone());
let v = FunctionFieldElement::y(f);
let du = u.divisor().unwrap();
let dv = v.divisor().unwrap();
let duv = u.mul(&v).unwrap().divisor().unwrap();
assert_eq!(duv, du.add(&dv).unwrap());
}
#[test]
fn a_constant_has_the_zero_divisor() {
let f = e1();
let d = FunctionFieldElement::constant(f, Rational::from((7, 3)))
.divisor()
.unwrap();
assert!(d.is_zero());
}
#[test]
fn genus_two_curve_divisor_of_y() {
let err = FunctionFieldElement::y(c2()).divisor().unwrap_err();
assert_eq!(err.code(), "E-FFLD-003");
}
#[test]
fn genus_two_curve_divisor_of_x() {
let f = c2();
let d = FunctionFieldElement::x(f).divisor().unwrap();
assert_eq!(d.coefficient(&pl(0, 1)), Integer::from(1));
assert_eq!(d.coefficient(&pl(0, -1)), Integer::from(1));
assert_eq!(d.coefficient(&Place::Infinity), Integer::from(-2));
}
#[test]
fn the_zero_function_has_no_divisor() {
let f = e1();
let z = FunctionFieldElement::constant(f, Rational::from(0));
assert_eq!(z.divisor().unwrap_err().code(), "E-FFLD-008");
}
#[test]
fn a_zero_denominator_is_refused() {
let f = e1();
let err = FunctionFieldElement::new(f, qp(&[1]), Vec::new(), Vec::new()).unwrap_err();
assert_eq!(err.code(), "E-FFLD-001");
}
#[test]
fn divisor_on_a_real_model_is_refused() {
let f = FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 0, 1]).unwrap();
let err = FunctionFieldElement::x(f).divisor().unwrap_err();
assert_eq!(err.code(), "E-FFLD-002");
}
#[test]
fn every_divisor_of_a_function_has_degree_zero() {
let f = e1();
let cases = [
(qp(&[0, 1]), Vec::new()),
(Vec::new(), qp(&[1])),
(qp(&[-1, 1]), Vec::new()),
(qp(&[0, 1]), qp(&[1])),
];
for (p, q) in cases {
let u = FunctionFieldElement::polynomial(f.clone(), p, q);
if let Ok(d) = u.divisor() {
assert_eq!(
d.degree(),
Integer::from(0),
"div({u:?}) has non-zero degree"
);
}
}
}
#[test]
fn to_expr_round_trips_through_the_pool() {
use crate::kernel::{Domain, ExprPool};
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let f = e1();
let u = FunctionFieldElement::new(f, qp(&[1]), qp(&[1]), qp(&[0, 1])).unwrap();
let e = u.to_expr(x, &pool);
let s = pool.display(e).to_string();
assert!(
s.contains("sqrt"),
"rendered element should show the radical: {s}"
);
}
}