Skip to main content

yui_core/conc/poly/
var3.rs

1//! Trivariate monomial `X^i Y^j Z^k`. With `I = isize` it is a Laurent monomial.
2
3use core::panic;
4use std::fmt::{Display, Debug};
5use std::ops::{AddAssign, Mul, MulAssign, DivAssign, SubAssign, Div, Add};
6use std::str::FromStr;
7use num_traits::{Zero, One, Pow, FromPrimitive, ToPrimitive};
8use auto_impl_ops::auto_ops;
9
10use crate::abst::{MathType, IndexType};
11use crate::lc::LcKey;
12use crate::util::parse_err::ParseErr;
13
14use super::{Mono, MonoOrd};
15use super::var::parse_mono_deg;
16use super::mvar::fmt_mono_n;
17
18/// A trivariate monomial `X^i Y^j Z^k`, with variable symbols `X`, `Y`, `Z`
19/// as const generics and exponent type `I` (`usize` or `isize`).
20#[derive(Clone, PartialEq, Eq, Hash, Default)]
21#[cfg_attr(feature = "serde", derive(serde_with::DeserializeFromStr))]
22pub struct Var3<const X: char, const Y: char, const Z: char, I>(
23    I, I, I
24);
25
26impl<const X: char, const Y: char, const Z: char, I> Var3<X, Y, Z, I> {
27    pub fn var_symbol(i: usize) -> char {
28        assert!(i < 3);
29        match i {
30            0 => X,
31            1 => Y,
32            2 => Z,
33            _ => panic!()
34        }
35    }
36
37    pub fn multi_deg(&self) -> (I, I, I)
38    where I: Copy {
39        (self.0, self.1, self.2)
40    }
41
42    pub fn deg_for(&self, i: usize) -> I
43    where I: Copy {
44        assert!(i < 3);
45        match i {
46            0 => self.0,
47            1 => self.1,
48            2 => self.2,
49            _ => panic!()
50        }
51    }
52
53    pub fn total_deg(&self) -> I
54    where I: Copy + for<'x> Add<&'x I, Output = I> {
55        self.0 + &self.1 + &self.2
56    }
57
58    pub fn eval<R>(&self, x: &R, y: &R, z: &R) -> R
59    where R: Mul<Output = R>, I: Copy, for<'x> &'x R: Pow<I, Output = R> {
60        x.pow(self.0) * y.pow(self.1) * z.pow(self.2)
61    }
62
63    fn to_string_u(&self, unicode: bool) -> String
64    where I: ToPrimitive {
65        let Var3(d0, d1, d2) = self;
66        let seq = [(X, d0), (Y, d1), (Z, d2)];
67        fmt_mono_n(seq, unicode)
68    }
69}
70
71impl<const X: char, const Y: char, const Z: char, I> From<(I, I, I)> for Var3<X, Y, Z, I> {
72    fn from(d: (I, I, I)) -> Self {
73        Self(d.0, d.1, d.2)
74    }
75}
76
77impl<const X: char, const Y: char, const Z: char, I> FromStr for Var3<X, Y, Z, I>
78where I: Zero + AddAssign + FromStr + FromPrimitive {
79    type Err = ParseErr;
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        use regex::Regex;
82
83        if s == "1" {
84            return Ok(Self(I::zero(), I::zero(), I::zero()))
85        }
86
87        let p = format!(r"({X}|{Y}|{Z})(\^\{{?-?[0-9]+\}}?)?");
88        let p_all = format!(r"^({p}\s?)+$");
89
90        let r = Regex::new(&p).unwrap();
91        let r_all = Regex::new(&p_all).unwrap();
92
93        if !r_all.is_match(s) {
94            return Err(ParseErr::invalid(s, &format!("a monomial in {X}, {Y}, {Z}")))
95        }
96
97        let mut deg = (I::zero(), I::zero(), I::zero());
98
99        for c in r.captures_iter(s) {
100            let x = &c[1];
101            let i = parse_mono_deg(x, &c[0]).ok_or_else(||
102                ParseErr::invalid(s, &format!("a monomial in {X}, {Y}, {Z}"))
103            )?;
104            if x.starts_with(X) {
105                deg.0 += i;
106            } else if x.starts_with(Y) {
107                deg.1 += i;
108            } else {
109                deg.2 += i;
110            }
111        };
112
113        Ok(Self::from(deg))
114    }
115}
116
117impl<const X: char, const Y: char, const Z: char, I> One for Var3<X, Y, Z, I>
118where I: for<'x >AddAssign<&'x I> + Zero {
119    fn one() -> Self {
120        Self::from((I::zero(), I::zero(), I::zero())) // x^0 = 1.
121    }
122}
123
124#[auto_ops]
125impl<const X: char, const Y: char, const Z: char, I> MulAssign<&Var3<X, Y, Z, I>> for Var3<X, Y, Z, I>
126where I: for<'x >AddAssign<&'x I> {
127    fn mul_assign(&mut self, rhs: &Var3<X, Y, Z, I>) {
128        self.0 += &rhs.0; // x^i * x^j = x^{i+j}
129        self.1 += &rhs.1;
130        self.2 += &rhs.2;
131    }
132}
133
134#[auto_ops]
135impl<const X: char, const Y: char, const Z: char, I> DivAssign<&Var3<X, Y, Z, I>> for Var3<X, Y, Z, I>
136where I: for<'x >SubAssign<&'x I> {
137    fn div_assign(&mut self, rhs: &Var3<X, Y, Z, I>) {
138        self.0 -= &rhs.0; // x^i / x^j = x^{i-j}
139        self.1 -= &rhs.1;
140        self.2 -= &rhs.2;
141    }
142}
143
144impl<const X: char, const Y: char, const Z: char, I> MonoOrd for Var3<X, Y, Z, I>
145where I: Copy + Eq + Ord + for<'x> Add<&'x I, Output = I> {
146    fn cmp_lex(&self, other: &Self) -> std::cmp::Ordering {
147        // must have x_0 > x_1 > x_2
148        I::cmp(&self.0, &other.0).then_with(||
149            I::cmp(&self.1, &other.1)
150        ).then_with(||
151            I::cmp(&self.2, &other.2)
152        )
153    }
154
155    fn cmp_grlex(&self, other: &Self) -> std::cmp::Ordering {
156        I::cmp(&self.total_deg(), &other.total_deg()).then_with(||
157            Self::cmp_lex(self, other)
158        )
159    }
160}
161
162impl<const X: char, const Y: char, const Z: char, I> PartialOrd for Var3<X, Y, Z, I>
163where I: Copy + Eq + Ord + for<'x> Add<&'x I, Output = I> {
164    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
165        Some(Self::cmp(self, other))
166    }
167}
168
169impl<const X: char, const Y: char, const Z: char, I> Ord for Var3<X, Y, Z, I>
170where I: Copy + Eq + Ord + for<'x> Add<&'x I, Output = I> {
171    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
172        Self::cmp_lex(self, other)
173    }
174}
175
176impl<const X: char, const Y: char, const Z: char, I> Display for Var3<X, Y, Z, I>
177where I: ToPrimitive {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        let s = self.to_string_u(true);
180        f.write_str(&s)
181    }
182}
183
184impl<const X: char, const Y: char, const Z: char, I> Debug for Var3<X, Y, Z, I>
185where I: ToPrimitive {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        Display::fmt(self, f)
188    }
189}
190
191#[cfg(feature = "serde")]
192impl<const X: char, const Y: char, const Z: char, I> serde::Serialize for Var3<X, Y, Z, I>
193where I: ToPrimitive {
194    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195    where S: serde::Serializer {
196        serializer.serialize_str(&self.to_string_u(false))
197    }
198}
199
200impl<const X: char, const Y: char, const Z: char, I> MathType for Var3<X, Y, Z, I>
201where I: IndexType + ToPrimitive {
202    fn math_symbol() -> String {
203        format!("{X}, {Y}, {Z}")
204    }
205}
206
207impl<const X: char, const Y: char, const Z: char, I> LcKey for Var3<X, Y, Z, I>
208where I: IndexType + Copy + for<'x> Add<&'x I, Output = I> + ToPrimitive {}
209
210macro_rules! impl_trivar_unsigned {
211    ($I:ty) => {
212        impl<const X: char, const Y: char, const Z: char> Mono for Var3<X, Y, Z, $I> {
213            type Deg = ($I, $I, $I);
214
215            fn deg(&self) -> Self::Deg {
216                (self.0, self.1, self.2)
217            }
218
219            fn is_unit(&self) -> bool {
220                self.0.is_zero() && self.1.is_zero() && self.2.is_zero()
221            }
222
223            fn inv(&self) -> Option<Self> { // (x^i)^{-1} = x^{-i}
224                if self.is_unit() {
225                    Some(Self(0, 0, 0))
226                } else {
227                    None
228                }
229            }
230
231            fn divides(&self, other: &Self) -> bool {
232                self.0 <= other.0 && self.1 <= other.1 && self.2 <= other.2
233            }
234        }
235    };
236}
237
238macro_rules! impl_trivar_signed {
239    ($I:ty) => {
240        impl<const X: char, const Y: char, const Z: char> Mono for Var3<X, Y, Z, $I> {
241            type Deg = ($I, $I, $I);
242
243            fn deg(&self) -> Self::Deg {
244                (self.0, self.1, self.2)
245            }
246
247            fn is_unit(&self) -> bool {
248                true
249            }
250
251            fn inv(&self) -> Option<Self> { // (x^i)^{-1} = x^{-i}
252                Some(Self(-self.0, -self.1, -self.2))
253            }
254
255            fn divides(&self, _other: &Self) -> bool {
256                true
257            }
258        }
259    };
260}
261
262impl_trivar_unsigned!(usize);
263impl_trivar_signed!  (isize);
264
265mod tex {
266    use crate::util::tex::TeX;
267    use super::*;
268
269    impl<const X: char, const Y: char, const Z: char, I> TeX for Var3<X, Y, Z, I>
270    where I: ToPrimitive {
271        fn tex_math_symbol() -> String {
272            format!("{},{},{}", X, Y, Z)
273        }
274        fn tex_string(&self) -> String {
275            self.to_string_u(false)
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn var_symbol() {
286        type M = Var3<'X','Y','Z',usize>;
287
288        assert_eq!(M::var_symbol(0), 'X');
289        assert_eq!(M::var_symbol(1), 'Y');
290        assert_eq!(M::var_symbol(2), 'Z');
291    }
292
293    #[test]
294    fn init() {
295        type M = Var3<'X','Y','Z',usize>;
296        let xyz = |i, j, k| M::from((i, j, k));
297
298        let d = xyz(2, 3, 1);
299
300        assert_eq!(d.0, 2);
301        assert_eq!(d.1, 3);
302        assert_eq!(d.2, 1);
303    }
304
305    #[test]
306    fn from_str() {
307        type M = Var3<'X','Y','Z',isize>;
308        let xyz = |i, j, k| M::from((i, j, k));
309
310        assert_eq!(M::from_str("1"), Ok(M::one()));
311        assert_eq!(M::from_str("X"), Ok(xyz(1, 0, 0)));
312        assert_eq!(M::from_str("Y"), Ok(xyz(0, 1, 0)));
313        assert_eq!(M::from_str("Z"), Ok(xyz(0, 0, 1)));
314        assert_eq!(M::from_str("X^2"), Ok(xyz(2, 0, 0)));
315        assert_eq!(M::from_str("Y^2"), Ok(xyz(0, 2, 0)));
316        assert_eq!(M::from_str("Z^2"), Ok(xyz(0, 0, 2)));
317        assert_eq!(M::from_str("XYZ"), Ok(xyz(1, 1, 1)));
318        assert_eq!(M::from_str("X^2Y^3Z"), Ok(xyz(2, 3, 1)));
319        assert_eq!(M::from_str("X^{21}Y^{-3}Z^{2}"), Ok(xyz(21, -3, 2)));
320        assert!(M::from_str("2").is_err());
321    }
322
323    #[test]
324    fn display() {
325        type M = Var3<'X','Y','Z',usize>;
326        let xyz = |i, j, k| M::from((i, j, k));
327
328        let d = xyz(0, 0, 0);
329        assert_eq!(&d.to_string(), "1");
330
331        let d = xyz(1, 0, 0);
332        assert_eq!(&d.to_string(), "X");
333
334        let d = xyz(2, 0, 0);
335        assert_eq!(&d.to_string(), "X²");
336
337        let d = xyz(0, 1, 0);
338        assert_eq!(&d.to_string(), "Y");
339
340        let d = xyz(0, 2, 0);
341        assert_eq!(&d.to_string(), "Y²");
342
343        let d = xyz(1, 1, 0);
344        assert_eq!(&d.to_string(), "XY");
345
346        let d = xyz(2, 3, 1);
347        assert_eq!(&d.to_string(), "X²Y³Z");
348    }
349
350    #[test]
351    fn neg_opt_unsigned() {
352        type M = Var3<'X','Y','Z',usize>;
353        let xyz = |i, j, k| M::from((i, j, k));
354
355        let d = xyz(0, 0, 0);
356        assert_eq!(d.inv(), Some(xyz(0, 0, 0)));
357
358        let d = xyz(1, 0, 0);
359        assert_eq!(d.inv(), None);
360
361        let d = xyz(0, 1, 0);
362        assert_eq!(d.inv(), None);
363
364        let d = xyz(0, 0, 1);
365        assert_eq!(d.inv(), None);
366    }
367
368    #[test]
369    fn neg_opt_signed() {
370        type M = Var3<'X','Y','Z',isize>;
371        let xyz = |i, j,k| M::from((i, j, k));
372
373        let d = xyz(0, 0, 0);
374        assert_eq!(d.inv(), Some(xyz(0, 0, 0)));
375
376        let d = xyz(1, 0, 0);
377        assert_eq!(d.inv(), Some(xyz(-1, 0, 0)));
378
379        let d = xyz(0, 1, 0);
380        assert_eq!(d.inv(), Some(xyz(0, -1, 0)));
381
382        let d = xyz(2, 3, 1);
383        assert_eq!(d.inv(), Some(xyz(-2, -3, -1)));
384    }
385
386    #[test]
387    fn eval() {
388        type M = Var3<'X','Y','Z',usize>;
389        let xyz = |i, j,k| M::from((i, j, k));
390
391        let d = xyz(0, 0, 0);
392        assert_eq!(d.eval::<i32>(&2, &3, &4), 1);
393
394        let d = xyz(1, 0, 0);
395        assert_eq!(d.eval::<i32>(&2, &3, &4), 2);
396
397        let d = xyz(0, 1, 0);
398        assert_eq!(d.eval::<i32>(&2, &3, &4), 3);
399
400        let d = xyz(1, 1, 1);
401        assert_eq!(d.eval::<i32>(&2, &3, &4), 24);
402
403        let d = xyz(2, 3, 1);
404        assert_eq!(d.eval::<i32>(&2, &3, &4), 432);
405    }
406
407    #[test]
408    fn cmp_lex() {
409        type M = Var3<'X','Y','Z',usize>;
410        let xyz = |i, j,k| M::from((i, j, k));
411
412        // x^2 y z > x y^2 z > x y z^2 > x > y^2 > z^3 > 1
413        assert!(Var3::cmp_lex(&xyz(2, 1, 1), &xyz(1, 2, 1)).is_gt());
414        assert!(Var3::cmp_lex(&xyz(1, 2, 1), &xyz(1, 1, 2)).is_gt());
415        assert!(Var3::cmp_lex(&xyz(1, 1, 2), &xyz(1, 0, 0)).is_gt());
416        assert!(Var3::cmp_lex(&xyz(1, 0, 0), &xyz(0, 2, 0)).is_gt());
417        assert!(Var3::cmp_lex(&xyz(0, 2, 0), &xyz(0, 0, 3)).is_gt());
418        assert!(Var3::cmp_lex(&xyz(0, 0, 3), &xyz(0, 0, 0)).is_gt());
419    }
420
421    #[test]
422    fn cmp_grlex() {
423        type M = Var3<'X','Y','Z',usize>;
424        let xyz = |i, j,k| M::from((i, j, k));
425
426        // x^2 y z > x y^2 z > x y z^2 > z^3 > y^2 > x > 1
427        assert!(Var3::cmp_grlex(&xyz(2, 1, 1), &xyz(1, 2, 1)).is_gt());
428        assert!(Var3::cmp_grlex(&xyz(1, 2, 1), &xyz(1, 1, 2)).is_gt());
429        assert!(Var3::cmp_grlex(&xyz(1, 1, 2), &xyz(0, 0, 3)).is_gt());
430        assert!(Var3::cmp_grlex(&xyz(0, 0, 3), &xyz(0, 2, 0)).is_gt());
431        assert!(Var3::cmp_grlex(&xyz(0, 2, 0), &xyz(1, 0, 0)).is_gt());
432        assert!(Var3::cmp_grlex(&xyz(1, 0, 0), &xyz(0, 0, 0)).is_gt());
433    }
434}