algebraeon_rings/polynomial/
symmetric.rs

1use super::multipoly_structure::*;
2use crate::{
3    polynomial::{MultiPolynomial, Variable},
4    structure::*,
5};
6use algebraeon_sets::structure::*;
7use itertools::Itertools;
8use std::{borrow::Borrow, collections::HashSet};
9
10pub fn ss_num(n: usize) -> String {
11    let mut ss = String::new();
12    for x in n.to_string().chars() {
13        match x {
14            '0' => ss += "₀",
15            '1' => ss += "₁",
16            '2' => ss += "₂",
17            '3' => ss += "₃",
18            '4' => ss += "₄",
19            '5' => ss += "₅",
20            '6' => ss += "₆",
21            '7' => ss += "₇",
22            '8' => ss += "₈",
23            '9' => ss += "₉",
24            c => ss += &mut c.to_string(),
25        }
26    }
27    ss
28}
29
30//express poly as a polynomial in the elementary symmetric polynomials in the given variables
31//return none if poly is not symmetric in the given variables
32impl<RS: IntegralDomainSignature, RSB: BorrowedStructure<RS>> MultiPolynomialStructure<RS, RSB>
33//TODO: replace integral domain with division ring structure
34where
35    MultiPolynomialStructure<RS, RSB>:
36        SetSignature<Set = MultiPolynomial<RS::Set>> + ToStringSignature,
37{
38    pub fn is_symmetric(
39        &self,
40        vars: Vec<impl Borrow<Variable>>,
41        poly: &MultiPolynomial<RS::Set>,
42    ) -> bool {
43        let vars_hash: HashSet<_> = vars.iter().map(|v| v.borrow().clone()).collect();
44        for v in poly.free_vars() {
45            if !vars_hash.contains(&v) {
46                return false;
47            }
48        }
49
50        let n = vars.len();
51        //find a generating set of Sn
52        let perms = match n {
53            0 => {
54                //trivial
55                vec![]
56            }
57            1 => {
58                //trivial
59                vec![vec![0]]
60            }
61            2 => {
62                // (1 2)
63                vec![
64                    (0..n)
65                        .map(|i| match i {
66                            0 => 1,
67                            1 => 0,
68                            x => x,
69                        })
70                        .collect(),
71                ]
72            }
73            n => {
74                // (1 2) and (1 2 ... n)
75                vec![
76                    (0..n)
77                        .map(|i| match i {
78                            0 => 1,
79                            1 => 0,
80                            x => x,
81                        })
82                        .collect(),
83                    (0..n).map(|i| (i + 1) % n).collect(),
84                ]
85            }
86        };
87
88        perms.into_iter().all(|perm| {
89            let perm_poly = poly.clone().apply_map_vars(
90                perm.into_iter()
91                    .enumerate()
92                    .map(|(i, j)| (vars[i].borrow().clone(), vars[j].borrow().clone()))
93                    .collect(),
94            );
95            self.equal(poly, &perm_poly)
96        })
97    }
98
99    pub fn elementary_symmetric(
100        &self,
101        n: usize,
102        vars: &[impl Borrow<Variable>],
103    ) -> MultiPolynomial<RS::Set> {
104        let mp_ring = self.coeff_ring().multivariable_polynomial_ring();
105        let mut e = mp_ring.zero();
106        for vs in algebraeon_sets::combinatorics::subsets_of_vec(vars.iter().collect(), n) {
107            let mut p = mp_ring.one();
108            for v in vs {
109                mp_ring.mul_mut(&mut p, &mp_ring.var(v.borrow().clone()));
110            }
111            mp_ring.add_mut(&mut e, &p);
112        }
113        e
114    }
115
116    fn as_elementary_symmetric_polynomials_homogeneous_impl(
117        &self,
118        vars: &Vec<Variable>,
119        p: &MultiPolynomial<RS::Set>,
120        e: &Vec<Variable>,
121    ) -> MultiPolynomial<RS::Set> {
122        // println!("e = {:?}", e);
123        // println!("p = {}", self.elem_to_string(p));
124
125        let (last_var, first_vars) = vars.split_last().unwrap();
126
127        let p_tilde = p.clone().evaluate_var_zero(last_var);
128        let r_sym =
129            self.as_elementary_symmetric_polynomials_impl(&first_vars.to_vec(), &p_tilde, e);
130        // println!("first_vars={:?} last_var={:?}", first_vars, last_var);
131        // println!("r_sym = {}", self.elem_to_string(&r_sym));
132        let r = self.multivariable_polynomial_ring().evaluate(
133            &r_sym.apply_map(|x| MultiPolynomial::constant(x.clone())),
134            (0..first_vars.len())
135                .map(|i| (e[i].clone(), self.elementary_symmetric(i + 1, vars)))
136                .collect(),
137        );
138        // println!("p_tilde = {}", self.elem_to_string(&p_tilde));
139        // println!("p_tilde_sym = {}", self.elem_to_string(&r_sym));
140        // println!("r = {}", self.elem_to_string(&r));
141        //q = p - r  is divisible by x1*x2*...*xn = en
142        let q = self.add(p, &self.neg(&r));
143        // println!("q = {}", self.elem_to_string(&q));
144        let en = self.product(vars.iter().map(|v| self.var(v.clone())).collect());
145        let en_sym = self.var(e.get(vars.len() - 1).unwrap().clone());
146        // println!("en = {}", self.elem_to_string(&en));
147        // println!("en_sym = {}", self.elem_to_string(&en_sym));
148        //p = r + en * q
149
150        let p_sym = self.add(
151            &r_sym,
152            &self.mul(
153                &en_sym,
154                &self.as_elementary_symmetric_polynomials_impl(
155                    vars,
156                    &self.try_divide(&q, &en).unwrap(),
157                    e,
158                ),
159            ),
160        );
161
162        // println!("p_sym = {}", self.elem_to_string(&p_sym));
163
164        #[allow(clippy::let_and_return)]
165        p_sym
166    }
167
168    fn as_elementary_symmetric_polynomials_impl(
169        &self,
170        vars: &Vec<Variable>,
171        poly: &MultiPolynomial<RS::Set>,
172        e: &Vec<Variable>,
173    ) -> MultiPolynomial<RS::Set> {
174        let mut total = self.zero();
175        for (d, hom_poly) in self.split_by_degree(poly.clone()) {
176            if d == 0 {
177                self.add_mut(&mut total, &hom_poly);
178            } else {
179                self.add_mut(
180                    &mut total,
181                    &self.as_elementary_symmetric_polynomials_homogeneous_impl(vars, &hom_poly, e),
182                );
183            }
184        }
185        total
186    }
187
188    //assume input is symmetrical
189    pub fn as_elementary_symmetric_polynomials_unchecked(
190        &self,
191        vars: Vec<impl Borrow<Variable>>,
192        poly: &MultiPolynomial<RS::Set>,
193    ) -> (Vec<Variable>, MultiPolynomial<RS::Set>) {
194        let e = (0..vars.len())
195            .map(|i| Variable::new(format!("e{}", ss_num(i + 1))))
196            .collect_vec();
197
198        #[cfg(debug_assertions)]
199        let vars_clone = vars.iter().map(|v| v.borrow().clone()).collect::<Vec<_>>();
200
201        let poly_sym = self.as_elementary_symmetric_polynomials_impl(
202            &vars.iter().map(|v| v.borrow().clone()).collect(),
203            poly,
204            &e,
205        );
206
207        #[cfg(debug_assertions)]
208        {
209            let poly_check = self.multivariable_polynomial_ring().evaluate(
210                &poly_sym.apply_map(|x| MultiPolynomial::constant(x.clone())),
211                (0..e.len())
212                    .map(|i| (e[i].clone(), self.elementary_symmetric(i + 1, &vars_clone)))
213                    .collect(),
214            );
215            assert!(self.equal(poly, &poly_check));
216        }
217
218        (e, poly_sym)
219    }
220
221    //return None if not symmetrical
222    pub fn as_elementary_symmetric_polynomials(
223        &self,
224        vars: Vec<impl Borrow<Variable>>,
225        poly: &MultiPolynomial<RS::Set>,
226    ) -> Option<(Vec<Variable>, MultiPolynomial<RS::Set>)> {
227        if self.is_symmetric(vars.iter().map(|v| v.borrow().clone()).collect(), poly) {
228            Some(self.as_elementary_symmetric_polynomials_unchecked(vars, poly))
229        } else {
230            None
231        }
232    }
233}
234
235impl<R: MetaType> MultiPolynomial<R>
236where
237    R::Signature: IntegralDomainSignature,
238    MultiPolynomialStructure<R::Signature, R::Signature>:
239        SetSignature<Set = Self> + ToStringSignature,
240{
241    pub fn is_symmetric(&self, vars: Vec<impl Borrow<Variable>>) -> bool {
242        Self::structure().is_symmetric(vars, self)
243    }
244
245    pub fn elementary_symmetric(n: usize, vars: Vec<impl Borrow<Variable>>) -> MultiPolynomial<R> {
246        Self::structure().elementary_symmetric(n, &vars)
247    }
248
249    pub fn as_elementary_symmetric_polynomials(
250        &self,
251        vars: Vec<impl Borrow<Variable>>,
252    ) -> Option<(Vec<Variable>, MultiPolynomial<R>)> {
253        Self::structure().as_elementary_symmetric_polynomials(vars, self)
254    }
255
256    pub fn as_elementary_symmetric_polynomials_unchecked(
257        &self,
258        vars: Vec<impl Borrow<Variable>>,
259    ) -> (Vec<Variable>, MultiPolynomial<R>) {
260        Self::structure().as_elementary_symmetric_polynomials_unchecked(vars, self)
261    }
262}
263
264#[cfg(test)]
265mod tests {
266
267    use super::*;
268
269    use algebraeon_nzq::*;
270
271    #[test]
272    fn test_ffosp() {
273        let x_var = Variable::new("x");
274        let y_var = Variable::new("y");
275        let z_var = Variable::new("z");
276
277        // let mut espm =
278        //     MultiPolynomial::<Integer>::symmetric_polynomial_manager(vec![&x_var, &y_var, &z_var]);
279
280        // println!(
281        //     "e0 = {}",
282        //     MultiPolynomial::<Integer>::elementary_symmetric(0, vec![&x_var, &y_var, &z_var])
283        // );
284        // println!(
285        //     "e1 = {}",
286        //     MultiPolynomial::<Integer>::elementary_symmetric(1, vec![&x_var, &y_var, &z_var])
287        // );
288        // println!(
289        //     "e2 = {}",
290        //     MultiPolynomial::<Integer>::elementary_symmetric(2, vec![&x_var, &y_var, &z_var])
291        // );
292        // println!(
293        //     "e3 = {}",
294        //     MultiPolynomial::<Integer>::elementary_symmetric(3, vec![&x_var, &y_var, &z_var])
295        // );
296
297        let x = &MultiPolynomial::<Integer>::var(x_var.clone()).into_ergonomic();
298        let y = &MultiPolynomial::<Integer>::var(y_var.clone()).into_ergonomic();
299        let z = &MultiPolynomial::<Integer>::var(z_var.clone()).into_ergonomic();
300
301        let f =
302            (x * x * y + x * x * z + y * y * x + y * y * z + z * z * x + z * z * y).into_verbose();
303
304        println!(
305            "f={} {}",
306            f,
307            f.is_symmetric(
308                vec![x_var.clone(), y_var.clone(), z_var.clone()]
309                    .into_iter()
310                    .collect()
311            )
312        );
313
314        let (e, g) = f
315            .as_elementary_symmetric_polynomials(vec![&x_var, &y_var, &z_var])
316            .unwrap();
317
318        println!("{:?}", e);
319        println!("{}", g);
320    }
321
322    #[test]
323    fn run() {
324        let x_var = Variable::new("x");
325        let y_var = Variable::new("y");
326        let z_var = Variable::new("z");
327
328        let x = &MultiPolynomial::<Integer>::var(x_var.clone()).into_ergonomic();
329        let y = &MultiPolynomial::<Integer>::var(y_var.clone()).into_ergonomic();
330        let z = &MultiPolynomial::<Integer>::var(z_var.clone()).into_ergonomic();
331
332        let f = (x.pow(12) + y.pow(12) + z.pow(12)).into_verbose();
333
334        let (e, g) = f
335            .as_elementary_symmetric_polynomials(vec![&x_var, &y_var, &z_var])
336            .unwrap();
337
338        println!("{:?}", e);
339        println!("{}", g);
340    }
341}