tilezz/
zzbase.rs

1//! In this module the core traits for all rings and fields are defined.
2
3use std::fmt::{Debug, Display};
4
5use num_complex::Complex64;
6use num_integer::Integer;
7use num_rational::Ratio;
8use num_traits::{FromPrimitive, One, PrimInt, ToPrimitive, Zero};
9
10use crate::gaussint::GaussInt;
11use crate::traits::{Ccw, Conj, InnerIntType, IntField, IntRing};
12use crate::zzsigned::ZSigned;
13
14/// We fix a general-purpose signed primitive integer size here. i64 is a natural choice.
15// NOTE: If the need arises, everything could be refactored to parametrize over this type.
16pub type MyInt = i64;
17
18/// Internally, ZZn use a Gaussian integer where all coefficients are
19/// a ratio with a fixed denominator (modulo simplification).
20pub type Frac = Ratio<MyInt>;
21
22/// Internally, the ring is constructed in a slightly non-standard way.
23/// For implementation convenience, instead of having each root twice (real and imaginary),
24/// this symmetry is pulled out by using Gaussian integers as coefficients for real-valued roots.
25pub type GInt = GaussInt<Frac>;
26
27/// Core parameters governing the behavior of a ring.
28#[derive(Debug)]
29pub struct ZZParams<'a> {
30    /// Squares of symbolic roots. IMPORTANT: we assume that the first one is 1
31    pub sym_roots_sqs: &'a [f64],
32    /// Labels of symbolic roots.
33    pub sym_roots_lbls: &'a [&'a str],
34    /// Number of symbolic roots in array sym_roots_sqs.
35    pub sym_roots_num: usize,
36    /// Number of steps in this complex integer ring that makes a full rotation.
37    pub full_turn_steps: i8,
38    /// Scaling factor 1/l common to all terms in the symbolic root number sum.
39    pub scaling_fac: MyInt,
40    /// Unit of rotation in coefficients of this ZZ type
41    pub ccw_unit_coeffs: &'a [[MyInt; 2]],
42}
43
44impl ZZParams<'static> {
45    /// Helper function to lift the param coeffs into a GaussInt of suitable type.
46    pub fn ccw_unit<T: PrimInt + Integer + IntRing>(&self) -> Vec<GaussInt<Ratio<T>>> {
47        let sc = T::from(self.scaling_fac).unwrap();
48        self.ccw_unit_coeffs
49            .into_iter()
50            .map(|x| {
51                GaussInt::new(
52                    Ratio::<T>::new_raw(T::from(x[0]).unwrap(), sc),
53                    Ratio::<T>::new_raw(T::from(x[1]).unwrap(), sc),
54                )
55            })
56            .collect()
57    }
58}
59
60// --------------------------------
61
62/// Assumptions about a scalar type to be used for coefficients representing ring elements.
63pub trait ZScalar:
64    IntField + InnerIntType + FromPrimitive + ZSigned + Conj + Debug + Display
65{
66}
67impl<T: IntField + InnerIntType + FromPrimitive + ZSigned + Conj + Debug + Display> ZScalar for T {}
68
69/// Common trait for various things required to implement cyclotomic rings.
70pub trait ZZBase {
71    type Scalar: ZScalar;
72    type Real: ZNum;
73
74    /// Return angle representing one full turn.
75    #[inline]
76    fn turn() -> i8 {
77        Self::zz_params().full_turn_steps
78    }
79
80    /// Return angle representing half a turn.
81    #[inline]
82    fn hturn() -> i8 {
83        Self::turn() / 2
84    }
85
86    /// Return whether this ring supports a quarter turn,
87    /// i.e. can represent the imaginary unit i.
88    ///
89    /// Useful in the rare situation when this information is needed at runtime.
90    #[inline]
91    fn has_qturn() -> bool {
92        Self::turn() % 4 == 0
93    }
94
95    /// Return angle representing a quarter turn (if ring supports it).
96    /// **NOTE:** In unsuitable rings this value will be incorrect (see `has_turn()`).
97    #[inline]
98    fn qturn() -> i8 {
99        Self::turn() / 4
100    }
101
102    /// Return angle representing a quarter turn (if ring supports it).
103    #[inline]
104    fn opt_qturn() -> Option<i8> {
105        if Self::has_qturn() {
106            Some(Self::qturn())
107        } else {
108            None
109        }
110    }
111
112    /// Return unit length vector pointing in direction of given angle.
113    fn unit(angle: i8) -> Self
114    where
115        Self: ZZNum,
116    {
117        Self::one() * Self::pow(&Self::ccw(), angle.rem_euclid(Self::turn()))
118    }
119
120    /// Raise to an integer power.
121    // NOTE: using i8 instead of u8 for convenience (angles use i8)
122    fn pow(&self, i: i8) -> Self
123    where
124        Self: ZCommon,
125    {
126        assert!(i >= 0, "Negative powers are not supported!");
127        let mut x = Self::one();
128        for _ in 0..i {
129            x = x * (*self);
130        }
131        return x;
132    }
133
134    /// Scalar multiplication.
135    #[inline]
136    fn scale<I: Integer + ToPrimitive>(&self, scalar: I) -> Self
137    where
138        Self: Sized,
139    {
140        let cs: Vec<Self::Scalar> = Self::zz_mul_scalar(self.zz_coeffs(), scalar.to_i64().unwrap());
141        Self::new(&cs)
142    }
143
144    /// Convert to a complex floating point number.
145    fn complex64(&self) -> Complex64;
146
147    // functions that can be implemented via zz_base_impl!
148    // --------
149    fn new(coeffs: &[Self::Scalar]) -> Self;
150
151    fn zz_coeffs(&self) -> &[Self::Scalar];
152    fn zz_coeffs_mut(&mut self) -> &mut [Self::Scalar];
153
154    fn zz_params() -> &'static ZZParams<'static>;
155    fn zz_mul_arrays(x: &[Self::Scalar], y: &[Self::Scalar]) -> Vec<Self::Scalar>;
156    fn zz_mul_scalar(arr: &[Self::Scalar], scalar: i64) -> Vec<Self::Scalar>;
157
158    // implementations for implementing other traits using zz_ops_impl!
159    // --------
160    #[inline]
161    fn zz_zero_vec() -> Vec<Self::Scalar> {
162        vec![Self::Scalar::zero(); Self::zz_params().sym_roots_num]
163    }
164
165    fn zz_one_vec() -> Vec<Self::Scalar> {
166        let mut ret = vec![Self::Scalar::zero(); Self::zz_params().sym_roots_num];
167        ret[0] = Self::Scalar::one();
168        ret
169    }
170
171    fn zz_add(&self, other: &Self) -> Vec<Self::Scalar> {
172        let mut ret = Self::zz_zero_vec();
173        for (i, (aval, bval)) in self.zz_coeffs().iter().zip(other.zz_coeffs()).enumerate() {
174            ret[i] = *aval + *bval;
175        }
176        ret
177    }
178
179    fn zz_sub(&self, other: &Self) -> Vec<Self::Scalar> {
180        let mut ret = Self::zz_zero_vec();
181        for (i, (aval, bval)) in self.zz_coeffs().iter().zip(other.zz_coeffs()).enumerate() {
182            ret[i] = *aval - *bval;
183        }
184        ret
185    }
186
187    fn zz_neg(&self) -> Vec<Self::Scalar> {
188        let mut ret = Self::zz_zero_vec();
189        for (i, val) in self.zz_coeffs().iter().enumerate() {
190            ret[i] = -(*val);
191        }
192        ret
193    }
194
195    #[inline]
196    fn zz_mul(&self, other: &Self) -> Vec<Self::Scalar> {
197        Self::zz_mul_arrays(self.zz_coeffs(), other.zz_coeffs())
198    }
199}
200
201pub trait ZZComplex {
202    /// Return true if the value is purely real.
203    fn is_real(&self) -> bool;
204
205    /// Return true if the value is purely imaginary.
206    fn is_imag(&self) -> bool;
207
208    /// Return true if the value is mixed real **and** imaginary.
209    fn is_complex(&self) -> bool {
210        !self.is_real() && !self.is_imag()
211    }
212
213    /// Return the real part of the value,
214    /// i.e. the value (z + z.conj()) / 2
215    fn re(&self) -> <Self as ZZBase>::Real
216    where
217        Self: ZZBase;
218
219    /// Return the imaginary part of the value (rotated onto the real axis),
220    /// i.e. the value (z - z.conj()) / 2i
221    fn im(&self) -> <Self as ZZBase>::Real
222    where
223        Self: ZZBase;
224
225    /// Split the value into its real and imaginary contributions.
226    /// Note that the imaginary component is converted to real.
227    ///
228    // NOTE: z = dot(1, z) + i*wedge(1, z), as dot(1, z) = Re(z), wedge(1, z) = Im(z)
229    fn re_im(&self) -> (<Self as ZZBase>::Real, <Self as ZZBase>::Real)
230    where
231        Self: ZZBase,
232    {
233        (self.re(), self.im())
234    }
235}
236
237/// Cyclotomic ring or its real part.
238pub trait ZCommon: ZZBase + InnerIntType + IntRing + Conj + Display {}
239
240/// Quadratic real extension corresponding to a cyclotomic ring
241/// (used to e.g. split a cyclotomic value into separate real and imaginary parts)
242pub trait ZNum: ZCommon + ZSigned {}
243
244/// A cyclotomic ring. You probably want to parametrize generic code over this trait.
245pub trait ZZNum: ZCommon + ZZComplex + Ccw {}
246
247// --------------------------------
248
249#[macro_export]
250macro_rules! zz_triv_impl {
251    ($trait_name: ident, $($t:ty)*) => ($(
252        impl $trait_name for $t {}
253    )*)
254}
255
256#[macro_export]
257macro_rules! zz_base_impl {
258    ($name:ident, $name_real:ident, $params:ident, $mul_func:ident, $re_signum_func:ident) => {
259        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
260        pub struct $name_real {
261            coeffs: [Frac; $params.sym_roots_num],
262        }
263        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
264        pub struct $name {
265            coeffs: [GInt; $params.sym_roots_num],
266        }
267
268        impl ZZBase for $name_real {
269            type Scalar = Frac;
270            type Real = $name_real;
271
272            #[inline]
273            fn zz_coeffs(&self) -> &[Self::Scalar] {
274                &self.coeffs
275            }
276
277            #[inline]
278            fn zz_coeffs_mut(&mut self) -> &mut [Self::Scalar] {
279                &mut self.coeffs
280            }
281
282            #[inline]
283            fn zz_params() -> &'static ZZParams<'static> {
284                &$params
285            }
286
287            #[inline]
288            fn zz_mul_arrays(x: &[Self::Scalar], y: &[Self::Scalar]) -> Vec<Self::Scalar> {
289                $mul_func(x, y)
290            }
291
292            #[inline]
293            fn zz_mul_scalar(x: &[Self::Scalar], scalar: i64) -> Vec<Self::Scalar> {
294                let sc: Self::Scalar = Self::Scalar::from(scalar);
295                x.into_iter().map(|c| *c * sc).collect()
296            }
297
298            fn new(coeffs: &[Self::Scalar]) -> Self {
299                let mut ret = Self {
300                    coeffs: [Self::Scalar::zero(); $params.sym_roots_num],
301                };
302                ret.coeffs.clone_from_slice(coeffs);
303                ret
304            }
305
306            fn complex64(&self) -> Complex64 {
307                let nums: Vec<Complex64> = self
308                    .zz_coeffs()
309                    .into_iter()
310                    .map(|x| {
311                        let re = x.to_f64().unwrap();
312                        Complex64::new(re, 0.0)
313                    })
314                    .collect();
315                let units: Vec<Complex64> = Self::zz_params()
316                    .sym_roots_sqs
317                    .into_iter()
318                    .map(|x| Complex64::new(x.sqrt(), 0.0))
319                    .collect();
320                let mut ret = Complex64::zero();
321                for (n, u) in nums.iter().zip(units.iter()) {
322                    ret += n * u;
323                }
324                ret
325            }
326        }
327        impl ZZBase for $name {
328            type Scalar = GaussInt<Frac>;
329            type Real = $name_real;
330
331            #[inline]
332            fn zz_coeffs(&self) -> &[Self::Scalar] {
333                &self.coeffs
334            }
335
336            #[inline]
337            fn zz_coeffs_mut(&mut self) -> &mut [Self::Scalar] {
338                &mut self.coeffs
339            }
340
341            #[inline]
342            fn zz_params() -> &'static ZZParams<'static> {
343                &$params
344            }
345
346            #[inline]
347            fn zz_mul_arrays(x: &[Self::Scalar], y: &[Self::Scalar]) -> Vec<Self::Scalar> {
348                $mul_func(x, y)
349            }
350
351            #[inline]
352            fn zz_mul_scalar(x: &[Self::Scalar], scalar: i64) -> Vec<Self::Scalar> {
353                let sc: Self::Scalar = Self::Scalar::from(scalar);
354                x.into_iter().map(|c| *c * sc).collect()
355            }
356
357            fn new(coeffs: &[Self::Scalar]) -> Self {
358                let mut ret = Self {
359                    coeffs: [Self::Scalar::zero(); $params.sym_roots_num],
360                };
361                ret.coeffs.clone_from_slice(coeffs);
362                ret
363            }
364
365            fn complex64(&self) -> Complex64 {
366                let nums: Vec<Complex64> = self
367                    .zz_coeffs()
368                    .into_iter()
369                    .map(|x| {
370                        let re = x.real.to_f64().unwrap();
371                        let im = x.imag.to_f64().unwrap();
372                        Complex64::new(re, im)
373                    })
374                    .collect();
375                let units: Vec<Complex64> = Self::zz_params()
376                    .sym_roots_sqs
377                    .into_iter()
378                    .map(|x| Complex64::new(x.sqrt(), 0.0))
379                    .collect();
380                let mut ret = Complex64::zero();
381                for (n, u) in nums.iter().zip(units.iter()) {
382                    ret += n * u;
383                }
384                ret
385            }
386        }
387
388        impl From<$name_real> for $name {
389            /// Lift real-valued ring value into the corresponding cyclomatic ring
390            fn from(value: $name_real) -> Self {
391                let cs: Vec<<$name as ZZBase>::Scalar> =
392                    value.zz_coeffs().iter().map(|z| (*z).into()).collect();
393                Self::new(cs.as_slice())
394            }
395        }
396
397        impl Conj for $name_real {
398            fn conj(&self) -> Self {
399                self.clone()
400            }
401            fn co_conj(&self) -> Self {
402                self.neg()
403            }
404        }
405        impl Conj for $name {
406            fn conj(&self) -> Self {
407                let cs: Vec<GInt> = self.zz_coeffs().iter().map(|c| c.conj()).collect();
408                Self::new(&cs)
409            }
410            fn co_conj(&self) -> Self {
411                let cs: Vec<GInt> = self.zz_coeffs().iter().map(|c| c.co_conj()).collect();
412                Self::new(&cs)
413            }
414        }
415
416        impl ZSigned for $name_real {
417            fn signum(&self) -> Self {
418                $re_signum_func(self)
419            }
420        }
421
422        impl ZNum for $name_real {}
423
424        impl ZZComplex for $name {
425            fn is_real(&self) -> bool {
426                self.zz_coeffs().iter().all(|c| c.imag.is_zero())
427            }
428
429            fn is_imag(&self) -> bool {
430                self.zz_coeffs().iter().all(|c| c.real.is_zero())
431            }
432
433            fn re(&self) -> <Self as ZZBase>::Real {
434                let cs: Vec<Frac> = self.zz_coeffs().iter().map(|c| c.real).collect();
435                $name_real::new(cs.as_slice())
436            }
437
438            fn im(&self) -> <Self as ZZBase>::Real {
439                let cs: Vec<Frac> = self.zz_coeffs().iter().map(|c| c.imag).collect();
440                $name_real::new(cs.as_slice())
441            }
442        }
443
444        impl Ccw for $name {
445            fn ccw() -> Self {
446                Self::new(Self::zz_params().ccw_unit().as_slice())
447            }
448            fn is_ccw(&self) -> bool {
449                *self == Self::ccw()
450            }
451        }
452        impl ZZNum for $name {}
453    };
454}
455
456#[macro_export]
457macro_rules! zz_ops_impl {
458    ($($t:ty)*) => ($(
459        impl Add<$t> for $t {
460            type Output = Self;
461            fn add(self, other: Self) -> Self {
462                Self::new(&Self::zz_add(&self, &other))
463            }
464        }
465        impl Sub<$t> for $t {
466            type Output = Self;
467            fn sub(self, other: Self) -> Self {
468                Self::new(&Self::zz_sub(&self, &other))
469            }
470        }
471        impl Neg for $t {
472            type Output = Self;
473            fn neg(self) -> Self {
474                Self::new(&Self::zz_neg(&self))
475            }
476        }
477        impl Mul<$t> for $t {
478            type Output = Self;
479            fn mul(self, other: Self) -> Self {
480                Self::new(&Self::zz_mul(&self, &other))
481            }
482        }
483
484        impl Zero for $t {
485            fn zero() -> Self {
486                Self::new(&Self::zz_zero_vec())
487            }
488            fn is_zero(&self) -> bool {
489                self.coeffs.to_vec() == Self::zz_zero_vec()
490            }
491        }
492        impl One for $t {
493            fn one() -> Self {
494                Self::new(&Self::zz_one_vec())
495            }
496            fn is_one(&self) -> bool {
497                self.coeffs.to_vec() == Self::zz_one_vec()
498            }
499        }
500
501        impl Display for $t {
502            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503                let nums: Vec<String> = self.coeffs.into_iter().map(|x| format!("{x}")).collect();
504                let units: Vec<String> = <$t>::zz_params().sym_roots_lbls.into_iter().map(|x| format!("sqrt({x})")).collect();
505                let parts: Vec<String> = nums.iter().zip(units.iter()).filter(|(x, _)| x != &"0").map(|(x, y)| {
506                    let is_real_unit = y == "sqrt(1)";
507                    if (x == "1") {
508                        if (is_real_unit) { "1".to_string() } else { y.to_string() }
509                    } else if (is_real_unit) {
510                        format!("{x}")
511                    } else {
512                        format!("({x})*{y}")
513                    }
514                }).collect();
515                let joined = parts.join(" + ");
516                let result = if (joined.is_empty()){ "0".to_string() } else { joined };
517                return write!(f, "{result}");
518            }
519        }
520
521        impl InnerIntType for $t {
522            type IntType = <<Self as ZZBase>::Scalar as InnerIntType>::IntType;
523        }
524
525        impl From<<$t as InnerIntType>::IntType> for $t {
526            fn from(value: <<Self as ZZBase>::Scalar as InnerIntType>::IntType) -> Self {
527                Self::one().scale(value)
528            }
529        }
530
531        impl IntRing for $t {}
532        impl ZCommon for $t {}
533    )*)
534}