use std::fmt;
use std::fmt::Debug;
use nalgebra::Complex;
use num::Zero;
use crate::arithmetic_utils::Field;
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum ModularError {
NonIntegerEntry,
NotInGroup,
NotEnoughConstraints,
SingularSystem,
NonFiniteCoefficient,
Unavailable,
InequivalentTransformationGroups,
}
impl fmt::Display for ModularError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonIntegerEntry => write!(f, "matrix entry is not an integer"),
Self::NotInGroup => write!(f, "matrix fails this group's membership condition"),
Self::NotEnoughConstraints => {
write!(
f,
"fewer constraints supplied than the dimension of the space"
)
}
Self::SingularSystem => {
write!(f, "constraints do not pin down a unique linear combination")
}
Self::NonFiniteCoefficient => write!(f, "a solved coefficient is not finite"),
Self::Unavailable => write!(f, "no implementation is available to compute this value"),
Self::InequivalentTransformationGroups => write!(
f,
"The two transformation groups are not the same group or do not have the same multiplier system."
),
}
}
}
impl std::error::Error for ModularError {}
pub trait ModularTransformationGroup<R: Field>: Sized {
#[allow(dead_code)]
fn new(raw_matrix: [[R; 2]; 2]) -> Result<Self, ModularError>;
#[allow(dead_code)]
fn transform_q(&self, q: &R) -> R;
#[allow(dead_code)]
fn transform_tau(&self, tau: &R) -> Option<R> {
let numerator = tau.clone().mul_add(self.raw_a(), self.raw_b());
let denominator = tau.clone().mul_add(self.raw_c(), self.raw_d());
if denominator.is_zero() {
None
} else {
let den_inv = denominator.inv();
Some(numerator * den_inv)
}
}
#[allow(dead_code)]
fn multiplier_system(&self, tau: &R) -> R;
fn is_trivial_multiplier_system() -> bool;
fn raw_a(&self) -> R;
fn raw_b(&self) -> R;
fn raw_c(&self) -> R;
fn raw_d(&self) -> R;
fn raw_matrix(&self) -> [[R; 2]; 2] {
[[self.raw_a(), self.raw_b()], [self.raw_c(), self.raw_d()]]
}
}
pub struct Sl2Z {
a: i128,
b: i128,
c: i128,
d: i128,
}
impl ModularTransformationGroup<Complex<f64>> for Sl2Z {
#[allow(clippy::many_single_char_names)]
fn new(raw_matrix: [[Complex<f64>; 2]; 2]) -> Result<Self, ModularError> {
const EPSILON: f64 = 1e-9;
let [[a, b], [c, d]] = raw_matrix;
if [a, b, c, d].into_iter().any(|x| {
if x.im.abs() > EPSILON {
true
} else {
let x = x.re;
(x - x.round()).abs() > EPSILON
}
}) {
return Err(ModularError::NonIntegerEntry);
}
#[allow(clippy::cast_possible_truncation)]
let (a, b, c, d) = (
a.re.round() as i128,
b.re.round() as i128,
c.re.round() as i128,
d.re.round() as i128,
);
if a * d - b * c != 1 {
return Err(ModularError::NotInGroup);
}
Ok(Self { a, b, c, d })
}
fn transform_q(&self, q: &Complex<f64>) -> Complex<f64> {
*q
}
fn multiplier_system(&self, _tau: &Complex<f64>) -> Complex<f64> {
Complex::new(1.0, 0.0)
}
#[allow(clippy::cast_precision_loss)]
fn raw_a(&self) -> Complex<f64> {
Complex {
re: self.a as f64,
im: 0.0,
}
}
#[allow(clippy::cast_precision_loss)]
fn raw_b(&self) -> Complex<f64> {
Complex {
re: self.b as f64,
im: 0.0,
}
}
#[allow(clippy::cast_precision_loss)]
fn raw_c(&self) -> Complex<f64> {
Complex {
re: self.c as f64,
im: 0.0,
}
}
#[allow(clippy::cast_precision_loss)]
fn raw_d(&self) -> Complex<f64> {
Complex {
re: self.d as f64,
im: 0.0,
}
}
fn is_trivial_multiplier_system() -> bool {
true
}
}
pub trait ModularForm<const TWICE_WEIGHT: usize, R: Field> {
type TransformationGroup: ModularTransformationGroup<R>;
fn extract_coeffs(&self, which_coeff: usize) -> Result<R, ModularError>;
fn evaluate_at(&self, q: &R) -> Result<R, ModularError>;
}
#[allow(dead_code)]
pub struct UnitModularForm;
impl ModularForm<0, Complex<f64>> for UnitModularForm {
type TransformationGroup = Sl2Z;
fn extract_coeffs(&self, which_coeff: usize) -> Result<Complex<f64>, ModularError> {
Ok(if which_coeff == 0 {
Complex { re: 1.0, im: 0.0 }
} else {
Complex::zero()
})
}
fn evaluate_at(&self, _q: &Complex<f64>) -> Result<Complex<f64>, ModularError> {
Ok(Complex { re: 1.0, im: 0.0 })
}
}