use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
use rug::{Integer, Rational};
use super::error::FunctionFieldError;
use super::field::FunctionField;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Place {
Finite {
x: Rational,
y: Rational,
},
Infinity,
}
impl Place {
pub fn finite(x: Rational, y: Rational) -> Self {
Place::Finite { x, y }
}
pub fn infinity() -> Self {
Place::Infinity
}
pub fn is_infinite(&self) -> bool {
matches!(self, Place::Infinity)
}
pub fn x(&self) -> Option<&Rational> {
match self {
Place::Finite { x, .. } => Some(x),
Place::Infinity => None,
}
}
pub fn y(&self) -> Option<&Rational> {
match self {
Place::Finite { y, .. } => Some(y),
Place::Infinity => None,
}
}
pub fn degree(&self) -> usize {
1
}
pub fn is_ramified(&self) -> bool {
match self {
Place::Finite { y, .. } => *y == 0,
Place::Infinity => true,
}
}
pub fn involution(&self) -> Place {
match self {
Place::Finite { x, y } => Place::Finite {
x: x.clone(),
y: -y.clone(),
},
Place::Infinity => Place::Infinity,
}
}
pub fn valuation_of_x_minus(&self, alpha: &Rational) -> i64 {
match self {
Place::Infinity => -2,
Place::Finite { x, y } => {
if x != alpha {
0
} else if *y == 0 {
2
} else {
1
}
}
}
}
}
impl fmt::Display for Place {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Place::Finite { x, y } => write!(f, "({x}, {y})"),
Place::Infinity => write!(f, "∞"),
}
}
}
#[derive(Clone, Debug)]
pub struct Divisor {
field: FunctionField,
terms: BTreeMap<Place, Integer>,
}
impl Divisor {
pub fn zero(field: FunctionField) -> Result<Self, FunctionFieldError> {
field.require_imaginary("Divisor")?;
Ok(Divisor {
field,
terms: BTreeMap::new(),
})
}
pub fn from_terms<I>(field: FunctionField, terms: I) -> Result<Self, FunctionFieldError>
where
I: IntoIterator<Item = (Place, Integer)>,
{
field.require_imaginary("Divisor")?;
let mut map: BTreeMap<Place, Integer> = BTreeMap::new();
for (place, coeff) in terms {
if let Place::Finite { x, y } = &place {
if !field.contains_point(x, y) {
return Err(FunctionFieldError::PlaceNotOnCurve {
x: x.to_string(),
y: y.to_string(),
});
}
}
*map.entry(place).or_insert_with(|| Integer::from(0)) += coeff;
}
map.retain(|_, v| *v != 0);
Ok(Divisor { field, terms: map })
}
pub fn field(&self) -> &FunctionField {
&self.field
}
pub fn coefficient(&self, place: &Place) -> Integer {
self.terms
.get(place)
.cloned()
.unwrap_or_else(|| Integer::from(0))
}
pub fn degree(&self) -> Integer {
self.terms
.values()
.fold(Integer::from(0), |acc, v| acc + v.clone())
}
pub fn support(&self) -> Vec<Place> {
self.terms.keys().cloned().collect()
}
pub fn terms(&self) -> Vec<(Place, Integer)> {
self.terms
.iter()
.map(|(p, c)| (p.clone(), c.clone()))
.collect()
}
pub fn support_len(&self) -> usize {
self.terms.len()
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn is_effective(&self) -> bool {
self.terms.values().all(|c| *c >= 0)
}
pub fn add(&self, other: &Divisor) -> Result<Divisor, FunctionFieldError> {
self.field.require_same(&other.field)?;
let mut terms = self.terms.clone();
for (p, c) in &other.terms {
*terms.entry(p.clone()).or_insert_with(|| Integer::from(0)) += c.clone();
}
terms.retain(|_, v| *v != 0);
Ok(Divisor {
field: self.field.clone(),
terms,
})
}
pub fn neg(&self) -> Divisor {
Divisor {
field: self.field.clone(),
terms: self
.terms
.iter()
.map(|(p, c)| (p.clone(), -c.clone()))
.collect(),
}
}
pub fn sub(&self, other: &Divisor) -> Result<Divisor, FunctionFieldError> {
self.add(&other.neg())
}
pub fn scale(&self, k: &Integer) -> Divisor {
if *k == 0 {
return Divisor {
field: self.field.clone(),
terms: BTreeMap::new(),
};
}
Divisor {
field: self.field.clone(),
terms: self
.terms
.iter()
.map(|(p, c)| (p.clone(), c.clone() * k.clone()))
.collect(),
}
}
pub fn involution(&self) -> Divisor {
let mut terms: BTreeMap<Place, Integer> = BTreeMap::new();
for (p, c) in &self.terms {
*terms
.entry(p.involution())
.or_insert_with(|| Integer::from(0)) += c.clone();
}
terms.retain(|_, v| *v != 0);
Divisor {
field: self.field.clone(),
terms,
}
}
pub fn leq(&self, other: &Divisor) -> Result<bool, FunctionFieldError> {
Ok(other.sub(self)?.is_effective())
}
pub(crate) fn finite_terms(&self) -> Vec<(Rational, Rational, Integer)> {
self.terms
.iter()
.filter_map(|(p, c)| match p {
Place::Finite { x, y } => Some((x.clone(), y.clone(), c.clone())),
Place::Infinity => None,
})
.collect()
}
pub(crate) fn infinity_coefficient(&self) -> Integer {
self.coefficient(&Place::Infinity)
}
}
impl PartialEq for Divisor {
fn eq(&self, other: &Self) -> bool {
self.field == other.field && self.terms == other.terms
}
}
impl Eq for Divisor {}
impl PartialOrd for Divisor {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.field != other.field {
return None;
}
let mut saw_less = false;
let mut saw_greater = false;
let mut places: Vec<&Place> = self.terms.keys().collect();
places.extend(other.terms.keys());
places.sort();
places.dedup();
for p in places {
match self.coefficient(p).cmp(&other.coefficient(p)) {
Ordering::Less => saw_less = true,
Ordering::Greater => saw_greater = true,
Ordering::Equal => {}
}
}
match (saw_less, saw_greater) {
(false, false) => Some(Ordering::Equal),
(true, false) => Some(Ordering::Less),
(false, true) => Some(Ordering::Greater),
(true, true) => None,
}
}
}
impl fmt::Display for Divisor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.terms.is_empty() {
return write!(f, "0");
}
let mut first = true;
for (p, c) in &self.terms {
if !first {
write!(f, " + ")?;
}
first = false;
if *c == 1 {
write!(f, "{p}")?;
} else {
write!(f, "{c}·{p}")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::AlkahestError;
fn genus1() -> FunctionField {
FunctionField::hyperelliptic_from_i64(&[0, -1, 0, 1]).unwrap()
}
fn p(x: i64, y: i64) -> Place {
Place::finite(Rational::from(x), Rational::from(y))
}
#[test]
fn degree_is_additive() {
let f = genus1();
let d1 = Divisor::from_terms(
f.clone(),
[
(p(0, 0), Integer::from(3)),
(Place::Infinity, Integer::from(-1)),
],
)
.unwrap();
let d2 = Divisor::from_terms(f.clone(), [(p(1, 0), Integer::from(2))]).unwrap();
assert_eq!(d1.degree(), Integer::from(2));
assert_eq!(d2.degree(), Integer::from(2));
assert_eq!(d1.add(&d2).unwrap().degree(), Integer::from(4));
}
#[test]
fn repeated_places_are_summed_and_zeroes_dropped() {
let f = genus1();
let d = Divisor::from_terms(
f,
[
(p(0, 0), Integer::from(2)),
(p(0, 0), Integer::from(-2)),
(p(1, 0), Integer::from(5)),
],
)
.unwrap();
assert_eq!(d.support_len(), 1);
assert_eq!(d.coefficient(&p(0, 0)), Integer::from(0));
assert_eq!(d.coefficient(&p(1, 0)), Integer::from(5));
}
#[test]
fn a_place_off_the_curve_is_refused() {
let f = genus1();
let err = Divisor::from_terms(f, [(p(2, 0), Integer::from(1))]).unwrap_err();
assert_eq!(err.code(), "E-FFLD-004");
}
#[test]
fn negation_and_scaling() {
let f = genus1();
let d = Divisor::from_terms(
f,
[
(p(0, 0), Integer::from(3)),
(Place::Infinity, Integer::from(-3)),
],
)
.unwrap();
assert_eq!(d.neg().coefficient(&p(0, 0)), Integer::from(-3));
assert_eq!(d.scale(&Integer::from(4)).degree(), Integer::from(0));
assert!(d.scale(&Integer::from(0)).is_zero());
assert!(d.add(&d.neg()).unwrap().is_zero());
}
#[test]
fn the_partial_order_is_partial() {
let f = genus1();
let a = Divisor::from_terms(f.clone(), [(p(0, 0), Integer::from(1))]).unwrap();
let b = Divisor::from_terms(f.clone(), [(p(1, 0), Integer::from(1))]).unwrap();
let ab = a.add(&b).unwrap();
assert!(a < ab);
assert!(ab > b);
assert_eq!(a.partial_cmp(&b), None);
assert!(a.leq(&ab).unwrap());
assert!(!a.leq(&b).unwrap());
}
#[test]
fn effectivity() {
let f = genus1();
let d = Divisor::from_terms(
f.clone(),
[
(p(0, 0), Integer::from(1)),
(Place::Infinity, Integer::from(-1)),
],
)
.unwrap();
assert!(!d.is_effective());
assert!(Divisor::zero(f).unwrap().is_effective());
}
#[test]
fn involution_flips_the_sheet_and_fixes_branch_points() {
let f = genus1();
let d = Divisor::from_terms(f.clone(), [(p(-1, 0), Integer::from(1))]).unwrap();
assert_eq!(d.involution(), d);
let g = FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 1]).unwrap(); let pt = Place::finite(Rational::from(2), Rational::from(3)); let d = Divisor::from_terms(g, [(pt.clone(), Integer::from(1))]).unwrap();
assert_eq!(
d.involution().support(),
vec![Place::finite(Rational::from(2), Rational::from(-3))]
);
}
#[test]
fn divisors_on_different_curves_do_not_mix() {
let a = Divisor::zero(genus1()).unwrap();
let b =
Divisor::zero(FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 1]).unwrap()).unwrap();
let err = a.add(&b).unwrap_err();
assert_eq!(err.code(), "E-FFLD-009");
assert_eq!(a.partial_cmp(&b), None);
}
#[test]
fn a_real_model_has_no_divisors_at_all() {
let f = FunctionField::hyperelliptic_from_i64(&[1, 0, 0, 0, 1]).unwrap();
let err = Divisor::zero(f).unwrap_err();
assert_eq!(err.code(), "E-FFLD-002");
}
#[test]
fn valuation_of_x_minus_alpha() {
assert_eq!(p(0, 0).valuation_of_x_minus(&Rational::from(0)), 2);
let q = Place::finite(Rational::from(2), Rational::from(3));
assert_eq!(q.valuation_of_x_minus(&Rational::from(2)), 1);
assert_eq!(q.valuation_of_x_minus(&Rational::from(5)), 0);
assert_eq!(Place::Infinity.valuation_of_x_minus(&Rational::from(0)), -2);
}
#[test]
fn display_is_readable() {
let f = genus1();
let d = Divisor::from_terms(
f,
[
(p(0, 0), Integer::from(2)),
(Place::Infinity, Integer::from(-2)),
],
)
.unwrap();
assert_eq!(d.to_string(), "2·(0, 0) + -2·∞");
}
}