polynomial_subspaces/
my_symmetrical_basis_pair.rs

1use core::ops::{Add, AddAssign, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
2
3use crate::{
4    fundamental_theorem::{
5        cubic_solve, quadratic_solve, quartic_solve, FindZeroError, FundamentalTheorem,
6    },
7    generic_polynomial::{
8        BasisIndexingType, DegreeType, DifferentiateError, Generic1DPoly, MonomialError,
9        SmallIntegers, SubspaceError,
10    },
11    ordinary_polynomial::MonomialBasisPolynomial,
12};
13
14/// store the coefficients of 1-t, t, (1-t)*s^1, t*s^1, (1-t)*s^2, t*s^2, ... in that order
15/// up to N of them
16/// s is shorthand for t*(1-t)
17/// this provides an alternative basis for the vector space of polynomials
18#[repr(transparent)]
19#[derive(Clone)]
20pub struct SymmetricalBasisPolynomial<const N: usize, T> {
21    pub(crate) coeffs: [T; N],
22}
23
24impl<const N: usize, T> SymmetricalBasisPolynomial<N, T>
25where
26    T: Clone
27        + Neg<Output = T>
28        + AddAssign<T>
29        + Add<Output = T>
30        + Mul<Output = T>
31        + MulAssign<T>
32        + From<SmallIntegers>
33        + Sub<Output = T>
34        + SubAssign<T>
35        + PartialEq<T>,
36{
37    /// with only knowing the const generic N, we can constrain the polynomial degree
38    /// as an upper bound, without worrying about if a coefficient was actually 0 or not
39    #[must_use]
40    pub const fn polynomial_degree_bound() -> usize {
41        // 1 -> 1-t -> 1
42        // 2 -> t,1-t -> 1
43        // 3 -> t,1-t,(1-t)^2*t -> 3
44        // 4 -> t,1-t,(1-t)^2*t,(1-t)*t^2 -> 3
45        if N % 2 == 0 {
46            N - 1
47        } else {
48            N
49        }
50    }
51
52    pub fn coeffs_view(&self) -> &[T; N] {
53        &self.coeffs
54    }
55
56    /// precompose with t <-> 1-t
57    /// can fail if N is odd, because
58    /// then the last coefficient should swap
59    /// with the one after it, but that doesn't exist
60    fn reverse(&mut self) {
61        #[allow(clippy::manual_assert)]
62        if N % 2 == 1 {
63            panic!("Only allowed to reverse in place on polynomials with even number of basis coefficients because they come in pairs for reversal");
64        }
65        for coeff_pair in self.coeffs.chunks_exact_mut(2) {
66            coeff_pair.reverse();
67        }
68    }
69
70    /// precompose with t <-> 1-t
71    /// check if it would fail rather than panic
72    /// that distinction can be done with compile time information
73    pub fn try_reverse(&mut self) -> bool {
74        if N % 2 == 0 {
75            return false;
76        }
77        self.reverse();
78        true
79    }
80
81    fn differentiate_single_hardcoded(n: usize) -> Option<Self> {
82        // hard coded for small n, because those are the ones that are used more often
83        if n < 2 {
84            let coeffs: [T; N] = core::array::from_fn(|idx| {
85                if idx > 1 {
86                    0.into()
87                } else {
88                    let ans: T = 1.into();
89                    if n == 0 {
90                        -ans
91                    } else {
92                        //
93                        ans
94                    }
95                }
96            });
97            return Some(Self { coeffs });
98        }
99        if n == 2 {
100            #[allow(clippy::match_same_arms)]
101            let coeffs: [T; N] = core::array::from_fn(|idx| match idx {
102                0 => 1.into(),
103                2 => (-3).into(),
104                3 => (-3).into(),
105                _ => 0.into(),
106            });
107            return Some(Self { coeffs });
108        }
109        if n == 3 {
110            #[allow(clippy::match_same_arms)]
111            let coeffs: [T; N] = core::array::from_fn(|idx| match idx {
112                1 => (-1).into(),
113                2 => (3).into(),
114                3 => (3).into(),
115                _ => 0.into(),
116            });
117            return Some(Self { coeffs });
118        }
119        if n == 4 {
120            #[allow(clippy::match_same_arms)]
121            let coeffs: [T; N] = core::array::from_fn(|idx| match idx {
122                2 => (2).into(),
123                4 => (-5).into(),
124                5 => (-5).into(),
125                _ => 0.into(),
126            });
127            return Some(Self { coeffs });
128        }
129        if n == 5 {
130            #[allow(clippy::match_same_arms)]
131            let coeffs: [T; N] = core::array::from_fn(|idx| match idx {
132                3 => (-2).into(),
133                4 => 5.into(),
134                5 => 5.into(),
135                _ => 0.into(),
136            });
137            return Some(Self { coeffs });
138        }
139        None
140    }
141
142    /// helper do differentiate when a single coefficient is 1 and the rest are 0
143    fn differentiate_single(n: usize) -> Self {
144        let hard_coded = Self::differentiate_single_hardcoded(n);
145        if let Some(got_hard_coded) = hard_coded {
146            return got_hard_coded;
147        }
148        // D_t (?*t^s_power*(1-t)^s_power)
149        // Term 1 : (D_t ?)*t^s_power*(1-t)^s_power = +-1   t^s_power*(1-t)^s_power
150        // Term 2 : ?*s_power*t^(s_power-1)*(1-t)^s_power = ?*s_power*(1-t)*    t^(s_power-1)*(1-t)^(s_power-1)
151        // Term 3 : ?*t^s_power*s_power*(-1)*(1-t)^(s_power-1) = -?*s_power*t*   t^(s_power-1)*(1-t)^(s_power-1)
152        let s_power = n >> 1;
153        #[allow(clippy::cast_possible_truncation)]
154        let s_power_t: T = ((n >> 1) as SmallIntegers).into();
155        // index of (1-t)*s^s_power
156        let where_one_minus_t_s_k = s_power << 1;
157        let mut answer = Self::create_zero_poly();
158        // Term 1 +-1   t^s_power*(1-t)^s_power
159        if n % 2 == 0 {
160            answer.coeffs[where_one_minus_t_s_k] -= 1.into();
161            answer.coeffs[where_one_minus_t_s_k + 1] -= 1.into();
162        } else {
163            answer.coeffs[where_one_minus_t_s_k] += 1.into();
164            answer.coeffs[where_one_minus_t_s_k + 1] += 1.into();
165        }
166        // Term 2 ?*s_power*(1-t)*    t^(s_power-1)*(1-t)^(s_power-1)
167        let shift_for_s_power_minus_1 = (s_power - 1) << 1;
168        let without_s_n_even = n % 2 == 0;
169        match Self::product_goes_01(without_s_n_even, true) {
170            Ok(x) => {
171                let which_coeff_x = x + (shift_for_s_power_minus_1);
172                answer.coeffs[which_coeff_x] += s_power_t.clone();
173                answer.coeffs[which_coeff_x + 1] += s_power_t.clone();
174            }
175            Err((x, y)) => {
176                let which_coeff_x = x + shift_for_s_power_minus_1;
177                answer.coeffs[which_coeff_x] += s_power_t.clone();
178                let which_coeff_y = y + shift_for_s_power_minus_1;
179                answer.coeffs[which_coeff_y] -= s_power_t.clone();
180                answer.coeffs[which_coeff_y + 1] -= s_power_t.clone();
181            }
182        }
183        // Term 3 -?*s_power*t*   t^(s_power-1)*(1-t)^(s_power-1)
184        match Self::product_goes_01(without_s_n_even, false) {
185            Ok(x) => {
186                let which_coeff_x = x + shift_for_s_power_minus_1;
187                answer.coeffs[which_coeff_x] -= s_power_t.clone();
188                answer.coeffs[which_coeff_x + 1] -= s_power_t;
189            }
190            Err((x, y)) => {
191                let which_coeff_x = x + shift_for_s_power_minus_1;
192                answer.coeffs[which_coeff_x] -= s_power_t.clone();
193                let which_coeff_y = y + shift_for_s_power_minus_1;
194                answer.coeffs[which_coeff_y] += s_power_t.clone();
195                answer.coeffs[which_coeff_y + 1] += s_power_t;
196            }
197        }
198        answer
199    }
200
201    const fn product_goes_01(
202        idx_is_zero: bool,
203        jdx_is_zero: bool,
204    ) -> Result<usize, (usize, usize)> {
205        match (idx_is_zero, jdx_is_zero) {
206            (true, true) => {
207                // (1-t)^2
208                // 1-t,t,(1-t)*t*(1-t),t*t*(1-t)
209                // (1-t)^2 = (1-t) - (1-t)*t
210                // (1-t)*t*(1-t) + t*t*(1-t) = t*(1-t)
211                // (1-t)^2 = -1 * (1-t)*t*(1-t) + -1 * t*t*(1-t) + (1-t)
212                Err((0, 2))
213            }
214            (true, false) => {
215                // (1-t)*t
216                Ok(2)
217            }
218            (false, true) => {
219                // t*(1-t)
220                Ok(2)
221            }
222            (false, false) => {
223                // t^2
224                // 1-t,t,(1-t)*t*(1-t),t*t*(1-t)
225                // t^2 = -1*(1-t)*t+t
226                // (1-t)*t*(1-t) + t*t*(1-t) = t*(1-t)
227                // t^2 = -1 * (1-t)*t*(1-t) + -1 * t*t*(1-t) + t
228                Err((1, 2))
229            }
230        }
231    }
232
233    /// when multiplying the term associated with `self.coeffs[idx]` and `self.coeffs[jdx]`
234    /// the possibilities are either 2 terms in the result or 3 terms
235    /// in the Ok case we have two terms both coming with + signs Ok(x) meaning x and x+1
236    /// in the Err case we have three terms with the first with a + sign and the other two with - signs
237    /// Err((x,y)) meaning x comes with + and y and y+1 come with - sign
238    #[allow(clippy::similar_names)]
239    const fn product_goes(idx: usize, jdx: usize) -> Result<usize, (usize, usize)> {
240        let power_of_s_idx = idx >> 1;
241        let power_of_s_jdx = jdx >> 1;
242        let answer = Self::product_goes_01(idx % 2 == 0, jdx % 2 == 0);
243        let products_power_of_s = power_of_s_idx + power_of_s_jdx;
244        if products_power_of_s == 0 {
245            return answer;
246        }
247        match answer {
248            Ok(mut idx) => {
249                idx += (products_power_of_s) << 1;
250                Ok(idx)
251            }
252            Err((mut x, mut y)) => {
253                x += products_power_of_s << 1;
254                y += products_power_of_s << 1;
255                Err((x, y))
256            }
257        }
258    }
259
260    pub fn multiply_by_t(
261        self,
262        sure_will_cancel: bool,
263        zero_pred: &impl Fn(&T) -> bool,
264    ) -> Option<Self> {
265        let mut answer = Self::create_zero_poly();
266        for (which, coeff) in self.coeffs.into_iter().enumerate() {
267            if zero_pred(&coeff) {
268                continue;
269            }
270            match Self::product_goes(which, 1) {
271                Ok(x) => {
272                    if x + 1 < N {
273                        answer.coeffs[x] += coeff.clone();
274                        answer.coeffs[x + 1] += coeff.clone();
275                    } else if sure_will_cancel {
276                        if x < N {
277                            answer.coeffs[x] += coeff.clone();
278                        }
279                    } else {
280                        return None;
281                    }
282                }
283                Err((y, z)) => {
284                    if z + 1 < N {
285                        answer.coeffs[y] += coeff.clone();
286                        answer.coeffs[z] -= coeff.clone();
287                        answer.coeffs[z + 1] -= coeff.clone();
288                    } else if sure_will_cancel {
289                        if y < N {
290                            answer.coeffs[y] += coeff.clone();
291                        }
292                        if z < N {
293                            answer.coeffs[z] -= coeff.clone();
294                        }
295                    } else {
296                        return None;
297                    }
298                }
299            }
300        }
301        Some(answer)
302    }
303
304    /// do the product of two polynomials
305    /// one with at most the first N coefficients in this basis
306    /// one with at most the first M coefficients in this basis
307    /// the product should fit within the first N
308    /// this can be done by making sure we pad self with 0's so N is large enough first
309    /// knowing when elements are 0 allows us to avoid multiplications by 0 and additions of 0, etc
310    /// `sure_will_cancel` means even if it looks like coefficients that are not in the first N,
311    ///     will be present in the middle of the computation, they will eventually cancel so no need to worry
312    fn try_product<const M: usize>(
313        &self,
314        rhs: &SymmetricalBasisPolynomial<M, T>,
315        zero_pred: &impl Fn(&T) -> bool,
316        sure_will_cancel: bool,
317    ) -> Option<Self> {
318        let mut answer = Self::create_zero_poly();
319        for (idx, factor_1_coeff) in self.coeffs.iter().enumerate() {
320            if zero_pred(factor_1_coeff) {
321                continue;
322            }
323            for (jdx, factor_2_coeff) in rhs.coeffs.iter().enumerate() {
324                if zero_pred(factor_2_coeff) {
325                    continue;
326                }
327                let product_these_coeffs = factor_1_coeff.clone() * factor_2_coeff.clone();
328                match Self::product_goes(idx, jdx) {
329                    Ok(a) => {
330                        if a + 1 >= N {
331                            if sure_will_cancel {
332                                if a < N {
333                                    answer.coeffs[a] += product_these_coeffs;
334                                }
335                            } else {
336                                return None;
337                            }
338                        } else {
339                            answer.coeffs[a] += product_these_coeffs.clone();
340                            answer.coeffs[a + 1] += product_these_coeffs;
341                        }
342                    }
343                    Err((a, b)) => {
344                        if b + 1 >= N {
345                            if sure_will_cancel {
346                                if a < N {
347                                    answer.coeffs[a] += product_these_coeffs.clone();
348                                }
349                                if b < N {
350                                    answer.coeffs[b] -= product_these_coeffs;
351                                }
352                            } else {
353                                return None;
354                            }
355                        } else {
356                            answer.coeffs[a] += product_these_coeffs.clone();
357                            answer.coeffs[b] -= product_these_coeffs.clone();
358                            answer.coeffs[b + 1] -= product_these_coeffs.clone();
359                        }
360                    }
361                }
362            }
363        }
364        Some(answer)
365    }
366
367    #[allow(clippy::result_unit_err)]
368    /// change the degree by padding with extra 0's for a larger M
369    /// or get rid of the 0's on the higher degree basis vectors to truncate to a smaller M
370    /// # Errors
371    /// if we are trying to shrink the size, but those terms being discarded were not 0
372    pub fn try_convert_degree<const M: usize>(
373        self,
374        zero_pred: &impl Fn(&T) -> bool,
375    ) -> Result<SymmetricalBasisPolynomial<M, T>, ()> {
376        if M < N {
377            match self.polynomial_degree(zero_pred) {
378                Some(big_degree)
379                    if usize::from(big_degree)
380                        > SymmetricalBasisPolynomial::<M, T>::polynomial_degree_bound() =>
381                {
382                    return Err(());
383                }
384                Some(_) => {
385                    if !self.coeffs[M..].iter().all(zero_pred) {
386                        return Err(());
387                    }
388                }
389                None => return Ok(SymmetricalBasisPolynomial::<M, T>::create_zero_poly()),
390            }
391        }
392        let coeffs = core::array::from_fn(|idx| {
393            if idx < N {
394                self.coeffs[idx].clone()
395            } else {
396                0.into()
397            }
398        });
399        Ok(SymmetricalBasisPolynomial::<M, T> { coeffs })
400    }
401
402    pub fn pretty_format(&self, variable: &str, zero_pred: &impl Fn(&T) -> bool) -> String
403    where
404        T: std::fmt::Debug,
405    {
406        let answers: [String; N] = core::array::from_fn(|idx| {
407            if zero_pred(&self.coeffs[idx]) {
408                "0".to_string()
409            } else {
410                let zeroth_part = format!("({:?})", self.coeffs[idx]);
411                let first_part = if idx % 2 == 0 {
412                    format!("(1 - {variable})")
413                } else {
414                    variable.to_string()
415                };
416                let s_power = idx >> 1;
417                if s_power == 0 {
418                    format!("{zeroth_part}*{first_part}")
419                } else {
420                    let second_part =
421                        format!("({variable}**{s_power})*((1 - {variable})**{s_power})");
422                    format!("{zeroth_part}*{first_part}*{second_part}")
423                }
424            }
425        });
426        answers.join(" + ")
427    }
428}
429
430impl<const N: usize, T> Generic1DPoly<T> for SymmetricalBasisPolynomial<N, T>
431where
432    T: Clone
433        + Neg<Output = T>
434        + AddAssign<T>
435        + Add<Output = T>
436        + Mul<Output = T>
437        + MulAssign<T>
438        + From<SmallIntegers>
439        + Sub<Output = T>
440        + SubAssign<T>
441        + PartialEq<T>,
442{
443    fn create_zero_poly() -> Self {
444        let coeffs = core::array::from_fn(|_| 0.into());
445        Self { coeffs }
446    }
447
448    fn create_monomial(
449        n: DegreeType,
450        zero_pred: &impl Fn(&T) -> bool,
451        surely_fits: bool,
452    ) -> Result<Self, MonomialError> {
453        if n == 0 {
454            if N < 2 {
455                return Err(MonomialError::DesiredMonomialNotInSpace(0));
456            }
457            let coeffs: [T; N] =
458                core::array::from_fn(|idx| if idx > 1 { 0.into() } else { 1.into() });
459            return Ok(Self { coeffs });
460        }
461        if n == 1 {
462            if N < 2 {
463                return Err(MonomialError::DesiredMonomialNotInSpace(1));
464            }
465            let coeffs: [T; N] =
466                core::array::from_fn(|idx| if idx == 1 { 1.into() } else { 0.into() });
467            return Ok(Self { coeffs });
468        }
469        let mut answer = Self::create_monomial(1, zero_pred, true)?;
470        for cur_power in 1..n {
471            // answer is t^cur_power
472            answer = answer
473                .multiply_by_t(surely_fits, zero_pred)
474                .ok_or(MonomialError::DesiredMonomialNotInSpace(cur_power + 1))?;
475            // answer is t^(cur_power+1)
476        }
477        Ok(answer)
478    }
479
480    fn evaluate_at_many<const POINT_COUNT: usize>(&self, ts: [T; POINT_COUNT]) -> [T; POINT_COUNT] {
481        // TODO test this
482        let s_values: [T; POINT_COUNT] =
483            core::array::from_fn(|idx| ts[idx].clone() * (Into::<T>::into(1) - ts[idx].clone()));
484        let mut cur_power_of_s: [T; POINT_COUNT] = core::array::from_fn(|_| 1.into());
485        let mut answers: [T; POINT_COUNT] = core::array::from_fn(|_| 0.into());
486        for term in self.coeffs.chunks_exact(2) {
487            let mut to_add: [T; POINT_COUNT] = core::array::from_fn(|_| term[0].clone());
488            // term[0]*(1-t)+term[1]*t = term[0] + (term[1]-term[0])*t
489            to_add.iter_mut().zip(&ts).zip(&cur_power_of_s).for_each(
490                |((to_add_idx, ts_idx), cur_power_of_s_idx)| {
491                    *to_add_idx += (term[1].clone() - term[0].clone()) * ts_idx.clone();
492                    *to_add_idx *= cur_power_of_s_idx.clone();
493                },
494            );
495            answers
496                .iter_mut()
497                .zip(to_add)
498                .for_each(|(cur_answer, to_add_idx)| {
499                    *cur_answer += to_add_idx;
500                });
501            cur_power_of_s
502                .iter_mut()
503                .zip(&s_values)
504                .for_each(|(cur_power_of_s_idx, s_idx)| {
505                    *cur_power_of_s_idx *= s_idx.clone();
506                });
507        }
508        if N % 2 == 1 {
509            let mut to_add: [T; POINT_COUNT] = core::array::from_fn(|_| self.coeffs[N - 1].clone());
510            // term[0]*(1-t)+term[1]*t = term[0] + (term[1]-term[0])*t
511
512            to_add.iter_mut().zip(&ts).zip(&cur_power_of_s).for_each(
513                |((to_add_idx, ts_idx), cur_power_of_s_idx)| {
514                    *to_add_idx -= self.coeffs[N - 1].clone() * ts_idx.clone();
515                    *to_add_idx *= cur_power_of_s_idx.clone();
516                },
517            );
518            answers
519                .iter_mut()
520                .zip(to_add)
521                .for_each(|(cur_answer, to_add_idx)| {
522                    *cur_answer += to_add_idx;
523                });
524        }
525        answers
526    }
527
528    fn evaluate_at(&self, t: T) -> T {
529        let one_t: T = 1.into();
530        let s = t.clone() * (one_t - t.clone());
531        let mut cur_power_of_s: T = 1.into();
532        let mut answer: T = 0.into();
533        for term in self.coeffs.chunks_exact(2) {
534            let mut to_add = term[0].clone();
535            // term[0]*(1-t)+term[1]*t = term[0] + (term[1]-term[0])*t
536            to_add += (term[1].clone() - term[0].clone()) * t.clone();
537            to_add *= cur_power_of_s.clone();
538            answer += to_add;
539            cur_power_of_s *= s.clone();
540        }
541        if N % 2 == 1 {
542            let mut to_add = self.coeffs[N - 1].clone();
543            to_add -= self.coeffs[N - 1].clone() * t.clone();
544            to_add *= cur_power_of_s.clone();
545            answer += to_add;
546        }
547        answer
548    }
549
550    fn evaluate_at_zero(&self) -> T {
551        self.coeffs[0].clone()
552    }
553
554    fn evaluate_at_one(&self) -> T {
555        self.coeffs[1].clone()
556    }
557
558    fn evaluate_at_neg_one(&self) -> T {
559        let mut cur_power_of_s: T = 1.into();
560        let mut answer: T = 0.into();
561        for term in self.coeffs.chunks_exact(2) {
562            let mut to_add = term[0].clone() * 2.into();
563            // term[0]*(1-(-1))+term[1]*(-1) = term[0]*2 - term[1]
564            to_add -= term[1].clone();
565            to_add *= cur_power_of_s.clone();
566            answer += to_add;
567            cur_power_of_s *= 2.into();
568            cur_power_of_s = -cur_power_of_s;
569        }
570        if N % 2 == 1 {
571            let mut to_add = self.coeffs[N - 1].clone() * 2.into();
572            to_add *= cur_power_of_s.clone();
573            answer += to_add;
574        }
575        answer
576    }
577
578    fn is_zero_polynomial(&self, zero_pred: &impl Fn(&T) -> bool) -> bool {
579        self.coeffs.iter().all(zero_pred)
580    }
581
582    fn is_constant_polynomial(&self, zero_pred: &impl Fn(&T) -> bool) -> bool {
583        let linear_coeff_truncated = if N == 1 {
584            -self.coeffs[0].clone()
585        } else {
586            self.coeffs[1].clone() - self.coeffs[0].clone()
587        };
588        zero_pred(&linear_coeff_truncated) && self.coeffs[1..].iter().all(zero_pred)
589    }
590
591    fn polynomial_degree(&self, zero_pred: &impl Fn(&T) -> bool) -> Option<DegreeType> {
592        let mut upper_bound = Self::polynomial_degree_bound();
593        if N % 2 == 1 && !zero_pred(&self.coeffs[N - 1]) {
594            #[allow(clippy::cast_possible_truncation)]
595            return Some(upper_bound as DegreeType);
596        }
597        while upper_bound > 1 {
598            // a*(1-t)*s^k + b*t*s^k
599            // ((b-a)*t + a)*s^k
600            let a = &self.coeffs[upper_bound - 1];
601            let b = &self.coeffs[upper_bound - 2];
602            let a_zero = zero_pred(a);
603            let b_zero = zero_pred(b);
604            #[allow(clippy::cast_possible_truncation)]
605            if a_zero && b_zero {
606                upper_bound -= 2;
607            } else if a_zero || b_zero {
608                return Some(upper_bound as DegreeType);
609            } else {
610                let b_minus_a = b.clone() - a.clone();
611                return if zero_pred(&b_minus_a) {
612                    Some((upper_bound - 1) as DegreeType)
613                } else {
614                    Some(upper_bound as DegreeType)
615                };
616            }
617        }
618        None
619    }
620
621    fn differentiate(self) -> Result<Self, DifferentiateError> {
622        if N % 2 == 1 && self.coeffs[N - 1] != 0.into() {
623            // has a term C*(1-t)*s^k with C nonzero
624            // so that it gives a 2k+1 degree polynomial
625            // but we don't have t*s*k and that is needed
626            // for the leading coefficient A of the 2k degree derivative
627            // namely we must have A(1-t)*s^k + A*t*s^k as the top two terms
628            // but t*s^k is not in this subspace
629            return Err(DifferentiateError::NotInTheSpace);
630        }
631        let mut answer = Self::create_zero_poly();
632        for (idx, cur_coeff) in self.coeffs.into_iter().enumerate() {
633            let mut new_term = Self::differentiate_single(idx);
634            new_term *= cur_coeff;
635            answer += new_term;
636        }
637        Ok(answer)
638    }
639
640    fn truncating_product(
641        &self,
642        rhs: &Self,
643        zero_pred: &impl Fn(&T) -> bool,
644        sure_will_cancel: bool,
645    ) -> Option<Self> {
646        self.try_product(rhs, zero_pred, sure_will_cancel)
647    }
648
649    fn all_basis_vectors(up_to: BasisIndexingType) -> Result<Vec<Self>, SubspaceError> {
650        if (up_to as usize) > N {
651            return Err(SubspaceError::NoSuchBasisVector(up_to - 1));
652        }
653        let mut answer = Vec::with_capacity(up_to as usize);
654        for degree in 0..up_to {
655            let coeffs: [T; N] = core::array::from_fn(|idx| {
656                #[allow(clippy::cast_possible_truncation)]
657                if (idx as DegreeType) == (degree as DegreeType) {
658                    1.into()
659                } else {
660                    0.into()
661                }
662            });
663            answer.push(Self { coeffs });
664        }
665        Ok(answer)
666    }
667
668    fn set_basis_vector_coeff(
669        &mut self,
670        which_coeff: BasisIndexingType,
671        new_coeff: T,
672    ) -> Result<(), SubspaceError> {
673        if (which_coeff as usize) >= N {
674            return Err(SubspaceError::NoSuchBasisVector(which_coeff));
675        }
676        self.coeffs[which_coeff as usize] = new_coeff;
677        Ok(())
678    }
679}
680
681impl<const N: usize, T> SymmetricalBasisPolynomial<N, T>
682where
683    T: Clone
684        + Neg<Output = T>
685        + AddAssign
686        + Add<Output = T>
687        + Mul<Output = T>
688        + MulAssign
689        + From<SmallIntegers>
690        + Sub<Output = T>
691        + SubAssign<T>
692        + DivAssign<T>,
693{
694    pub fn base_change<U>(self) -> SymmetricalBasisPolynomial<N, U>
695    where
696        U: Clone
697            + Neg<Output = U>
698            + AddAssign<U>
699            + Add<U, Output = U>
700            + Mul<U, Output = U>
701            + MulAssign<U>
702            + From<SmallIntegers>
703            + Sub<U, Output = U>
704            + SubAssign<U>
705            + From<T>,
706    {
707        SymmetricalBasisPolynomial::<N, U> {
708            coeffs: core::array::from_fn(|idx| self.coeffs[idx].clone().into()),
709        }
710    }
711}
712
713impl<const N: usize, T> FundamentalTheorem<T> for SymmetricalBasisPolynomial<N, T>
714where
715    T: Clone
716        + Neg<Output = T>
717        + AddAssign<T>
718        + Add<Output = T>
719        + Mul<Output = T>
720        + MulAssign<T>
721        + From<SmallIntegers>
722        + Sub<Output = T>
723        + SubAssign<T>
724        + DivAssign<T>
725        + PartialEq<T>,
726{
727    /// zeros of the polynomial along with their
728    /// multiplicities
729    /// the second term is 1 below the multiplicity
730    /// so that we can still use 0 as something meaningful -> 0 is a simple root of multiplicity 1
731    fn find_zeros(
732        &self,
733        zero_pred: &impl Fn(&T) -> bool,
734        my_sqrt: &impl Fn(&T) -> Option<T>,
735        my_cube_root: &impl Fn(&T) -> (Option<T>, Option<T>),
736    ) -> Result<Vec<(T, usize)>, FindZeroError> {
737        let degree = self.polynomial_degree(zero_pred);
738        match degree {
739            Some(0) => Ok(vec![]),
740            Some(1) => {
741                let constant_term = self.evaluate_at_zero();
742                let linear_coeff = if N == 1 {
743                    -self.coeffs[0].clone()
744                } else {
745                    self.coeffs[1].clone() - self.coeffs[0].clone()
746                };
747                // linear_coeff*t+constant_term = 0
748                let mut only_zero = -constant_term;
749                only_zero /= linear_coeff;
750                Ok(vec![(only_zero, 0)])
751            }
752            Some(2) => {
753                // in order to be quadratic both coeffs[2] and coeffs[3] must be present
754                // without coeffs[3] but with nonzero coeffs[2] it would be cubic
755                // and if coeffs[2] was 0 it would be linear, constant or 0
756                let constant_term = self.evaluate_at_zero();
757                //self.coeffs[0]*(1-t) + self.coeffs[1]*t + self.coeffs[2]*(1-t)*t*(1-t) + self.coeffs[3]*t*(1-t)*t
758                let two_t: T = 2.into();
759                let linear_coeff =
760                    self.coeffs[1].clone() - self.coeffs[0].clone() + self.coeffs[2].clone();
761                let quadratic_coeff =
762                    -two_t.clone() * self.coeffs[2].clone() + self.coeffs[3].clone();
763                Ok(quadratic_solve(
764                    constant_term,
765                    linear_coeff,
766                    quadratic_coeff,
767                    zero_pred,
768                    my_sqrt,
769                ))
770            }
771            Some(3) => {
772                let constant_term = self.evaluate_at_zero();
773                //self.coeffs[0]*(1-t) + self.coeffs[1]*t + self.coeffs[2]*(1-t)*t*(1-t) + self.coeffs[3]*t*(1-t)*t
774                let two_t: T = 2.into();
775                let linear_coeff =
776                    self.coeffs[1].clone() - self.coeffs[0].clone() + self.coeffs[2].clone();
777                let (quadratic_coeff, cubic_coeff) = if N < 4 {
778                    let quadratic_coeff = -two_t.clone() * self.coeffs[2].clone();
779                    let cubic_coeff = -self.coeffs[2].clone();
780                    (quadratic_coeff, cubic_coeff)
781                } else {
782                    let quadratic_coeff =
783                        -two_t.clone() * self.coeffs[2].clone() + self.coeffs[3].clone();
784                    let cubic_coeff = -(self.coeffs[2].clone() + self.coeffs[3].clone());
785                    (quadratic_coeff, cubic_coeff)
786                };
787                cubic_solve(
788                    constant_term,
789                    linear_coeff,
790                    quadratic_coeff,
791                    cubic_coeff,
792                    zero_pred,
793                    my_sqrt,
794                    my_cube_root,
795                )
796            }
797            Some(4) => {
798                let constant_term = self.evaluate_at_zero();
799                // the terms from before and
800                // self.coeffs[4]*(1-t)*(1-t)^2*t^2 + self.coeffs[5]*t*(1-t)^2*t^2
801                // with -self.coeffs[4]+self.coeffs[5] = 0 to cancel out the t^5 term
802                let two_t: T = 2.into();
803                let linear_coeff =
804                    self.coeffs[1].clone() - self.coeffs[0].clone() + self.coeffs[2].clone();
805                let (mut quadratic_coeff, mut cubic_coeff) = if N < 4 {
806                    let quadratic_coeff = -two_t.clone() * self.coeffs[2].clone();
807                    let cubic_coeff = -self.coeffs[2].clone();
808                    (quadratic_coeff, cubic_coeff)
809                } else {
810                    let quadratic_coeff =
811                        -two_t.clone() * self.coeffs[2].clone() + self.coeffs[3].clone();
812                    let cubic_coeff = -(self.coeffs[2].clone() + self.coeffs[3].clone());
813                    (quadratic_coeff, cubic_coeff)
814                };
815                quadratic_coeff += self.coeffs[4].clone();
816                let three_t: T = 3.into();
817                let three_coeff_4 = three_t * self.coeffs[4].clone();
818                cubic_coeff += self.coeffs[5].clone() - three_coeff_4.clone();
819                let quartic_coeff = -(three_coeff_4 + two_t * self.coeffs[5].clone());
820                quartic_solve(
821                    constant_term,
822                    linear_coeff,
823                    quadratic_coeff,
824                    cubic_coeff,
825                    quartic_coeff,
826                    zero_pred,
827                    my_sqrt,
828                    my_cube_root,
829                )
830            }
831            Some(x) if x > 4 => Err(FindZeroError::AbelRuffiniUnsolvability(x)),
832            None => Err(FindZeroError::EverythingIsAZeroForZeroPolynomial),
833            _ => {
834                unreachable!("x>4 exhausted all the other Some cases")
835            }
836        }
837    }
838}
839
840impl<const N: usize, T> SubAssign<T> for SymmetricalBasisPolynomial<N, T>
841where
842    T: Clone
843        + Neg<Output = T>
844        + AddAssign
845        + SubAssign
846        + Add<Output = T>
847        + Mul<Output = T>
848        + MulAssign
849        + From<SmallIntegers>
850        + Sub<Output = T>,
851{
852    fn sub_assign(&mut self, rhs: T) {
853        assert!(N >= 1, "The zero subspace");
854        assert!(N >= 2, "The subspace spanned by only (1-t)");
855        self.coeffs[0] -= rhs.clone();
856        self.coeffs[1] -= rhs.clone();
857    }
858}
859
860impl<const N: usize, T> AddAssign<T> for SymmetricalBasisPolynomial<N, T>
861where
862    T: Clone
863        + Neg<Output = T>
864        + AddAssign
865        + SubAssign
866        + Add<Output = T>
867        + Mul<Output = T>
868        + MulAssign
869        + From<SmallIntegers>
870        + Sub<Output = T>,
871{
872    fn add_assign(&mut self, rhs: T) {
873        assert!(N >= 1, "The zero subspace");
874        assert!(N >= 2, "The subspace spanned by only (1-t)");
875        self.coeffs[0] += rhs.clone();
876        self.coeffs[1] += rhs.clone();
877    }
878}
879
880impl<const N: usize, T> MulAssign<T> for SymmetricalBasisPolynomial<N, T>
881where
882    T: Clone
883        + Neg<Output = T>
884        + AddAssign
885        + Add<Output = T>
886        + Mul<Output = T>
887        + MulAssign
888        + From<SmallIntegers>
889        + Sub<Output = T>,
890{
891    fn mul_assign(&mut self, rhs: T) {
892        for cur_coeff in &mut self.coeffs {
893            *cur_coeff *= rhs.clone();
894        }
895    }
896}
897
898impl<const N: usize, T> Mul<T> for SymmetricalBasisPolynomial<N, T>
899where
900    T: Clone
901        + Neg<Output = T>
902        + AddAssign
903        + Add<Output = T>
904        + Mul<Output = T>
905        + MulAssign
906        + From<SmallIntegers>
907        + Sub<Output = T>,
908{
909    type Output = Self;
910    fn mul(mut self, rhs: T) -> Self::Output {
911        self *= rhs;
912        self
913    }
914}
915
916impl<const N: usize, T> Sub<T> for SymmetricalBasisPolynomial<N, T>
917where
918    T: Clone
919        + Neg<Output = T>
920        + AddAssign
921        + Add<Output = T>
922        + Mul<Output = T>
923        + MulAssign
924        + From<SmallIntegers>
925        + Sub<Output = T>,
926{
927    type Output = Self;
928    fn sub(mut self, rhs: T) -> Self::Output {
929        assert!(N >= 1, "The zero subspace");
930        assert!(N >= 2, "The subspace spanned by only (1-t)");
931        self.coeffs[0] = self.coeffs[0].clone() - rhs.clone();
932        self.coeffs[1] = self.coeffs[1].clone() - rhs.clone();
933        self
934    }
935}
936
937impl<const N: usize, T> Add<T> for SymmetricalBasisPolynomial<N, T>
938where
939    T: Clone
940        + Neg<Output = T>
941        + AddAssign
942        + Add<Output = T>
943        + Mul<Output = T>
944        + MulAssign
945        + From<SmallIntegers>
946        + Sub<Output = T>,
947{
948    type Output = Self;
949    fn add(mut self, rhs: T) -> Self::Output {
950        assert!(N >= 1, "The zero subspace");
951        assert!(N >= 2, "The subspace spanned by only (1-t)");
952        self.coeffs[0] += rhs.clone();
953        self.coeffs[1] += rhs;
954        self
955    }
956}
957
958impl<const N: usize, T> Add<Self> for SymmetricalBasisPolynomial<N, T>
959where
960    T: Clone
961        + Neg<Output = T>
962        + AddAssign
963        + Add<Output = T>
964        + Mul<Output = T>
965        + MulAssign
966        + From<SmallIntegers>
967        + Sub<Output = T>,
968{
969    type Output = Self;
970
971    fn add(mut self, rhs: Self) -> Self::Output {
972        self += rhs;
973        self
974    }
975}
976
977impl<const N: usize, T> AddAssign for SymmetricalBasisPolynomial<N, T>
978where
979    T: Clone
980        + Neg<Output = T>
981        + AddAssign
982        + Add<Output = T>
983        + Mul<Output = T>
984        + MulAssign
985        + From<SmallIntegers>
986        + Sub<Output = T>,
987{
988    fn add_assign(&mut self, rhs: Self) {
989        for (idx, cur_coeff) in rhs.coeffs.into_iter().enumerate() {
990            self.coeffs[idx] += cur_coeff;
991        }
992    }
993}
994
995impl<const N: usize, T> Sub<Self> for SymmetricalBasisPolynomial<N, T>
996where
997    T: Clone
998        + Neg<Output = T>
999        + AddAssign
1000        + Add<Output = T>
1001        + Mul<Output = T>
1002        + MulAssign
1003        + From<SmallIntegers>
1004        + Sub<Output = T>
1005        + SubAssign,
1006{
1007    type Output = Self;
1008
1009    fn sub(mut self, rhs: Self) -> Self::Output {
1010        self -= rhs;
1011        self
1012    }
1013}
1014
1015impl<const N: usize, T> SubAssign for SymmetricalBasisPolynomial<N, T>
1016where
1017    T: Clone
1018        + Neg<Output = T>
1019        + AddAssign
1020        + Add<Output = T>
1021        + Mul<Output = T>
1022        + MulAssign
1023        + From<SmallIntegers>
1024        + Sub<Output = T>
1025        + SubAssign,
1026{
1027    fn sub_assign(&mut self, rhs: Self) {
1028        for (idx, cur_coeff) in rhs.coeffs.into_iter().enumerate() {
1029            self.coeffs[idx] -= cur_coeff;
1030        }
1031    }
1032}
1033
1034impl<const N: usize, T> Neg for SymmetricalBasisPolynomial<N, T>
1035where
1036    T: Clone
1037        + Neg<Output = T>
1038        + AddAssign<T>
1039        + Add<Output = T>
1040        + Mul<Output = T>
1041        + MulAssign<T>
1042        + From<SmallIntegers>
1043        + Sub<Output = T>
1044        + SubAssign<T>
1045        + PartialEq<T>,
1046{
1047    type Output = Self;
1048
1049    fn neg(self) -> Self::Output {
1050        let mut answer = Self::create_zero_poly();
1051        answer -= self;
1052        answer
1053    }
1054}
1055
1056impl<const N: usize, T> TryFrom<MonomialBasisPolynomial<T>> for SymmetricalBasisPolynomial<N, T>
1057where
1058    T: Clone
1059        + Neg<Output = T>
1060        + AddAssign<T>
1061        + Add<Output = T>
1062        + Mul<Output = T>
1063        + MulAssign<T>
1064        + From<SmallIntegers>
1065        + Sub<Output = T>
1066        + SubAssign<T>
1067        + PartialEq<T>,
1068{
1069    type Error = MonomialError;
1070
1071    fn try_from(value: MonomialBasisPolynomial<T>) -> Result<Self, Self::Error> {
1072        let mut answer = Self::create_zero_poly();
1073        for term in value.coeffs {
1074            match Self::create_monomial(term.0, &|_| false, false) {
1075                Ok(monomial_symmetrized) => {
1076                    answer += monomial_symmetrized * term.1;
1077                }
1078                Err(e) => {
1079                    return Err(e);
1080                }
1081            }
1082        }
1083        Ok(answer)
1084    }
1085}
1086
1087mod test {
1088
1089    #[allow(clippy::float_cmp)]
1090    #[test]
1091    fn test_product_goes_small() {
1092        use super::SymmetricalBasisPolynomial;
1093        use crate::generic_polynomial::{Generic1DPoly, SmallIntegers};
1094        let t_squared = SymmetricalBasisPolynomial::<6, f64> {
1095            coeffs: [0., 1., -1., -1., 0., 0.],
1096        };
1097        let s = SymmetricalBasisPolynomial::<6, f64> {
1098            coeffs: [0., 0., 1., 1., 0., 0.],
1099        };
1100        let one_minus_t_squared = SymmetricalBasisPolynomial::<6, f64> {
1101            coeffs: [1., 0., -1., -1., 0., 0.],
1102        };
1103        let expected_results = [[one_minus_t_squared, s.clone()], [s, t_squared]];
1104        let into_poly = |current: Result<usize, (usize, usize)>| match current {
1105            Ok(x) => {
1106                let mut answer = SymmetricalBasisPolynomial::<6, f64>::create_zero_poly();
1107                answer.coeffs[x] += Into::<f64>::into(1 as SmallIntegers);
1108                answer.coeffs[x + 1] += Into::<f64>::into(1 as SmallIntegers);
1109                answer
1110            }
1111            Err((x, y)) => {
1112                let mut answer = SymmetricalBasisPolynomial::<6, f64>::create_zero_poly();
1113                answer.coeffs[x] += Into::<f64>::into(1 as SmallIntegers);
1114                answer.coeffs[y] -= Into::<f64>::into(1 as SmallIntegers);
1115                answer.coeffs[y + 1] -= Into::<f64>::into(1 as SmallIntegers);
1116                answer
1117            }
1118        };
1119        for idx in [0, 1] {
1120            for jdx in [0, 1] {
1121                let current = SymmetricalBasisPolynomial::<6, f64>::product_goes(idx, jdx);
1122                assert_eq!(
1123                    into_poly(current).coeffs,
1124                    expected_results[idx][jdx].coeffs,
1125                    "{idx} {jdx}"
1126                );
1127            }
1128        }
1129    }
1130
1131    #[test]
1132    #[allow(clippy::float_cmp, clippy::too_many_lines)]
1133    fn test_differentiate_single_small() {
1134        use super::SymmetricalBasisPolynomial;
1135        use crate::generic_polynomial::Generic1DPoly;
1136        let one = SymmetricalBasisPolynomial::<6, f64> {
1137            coeffs: [1., 1., 0., 0., 0., 0.],
1138        };
1139        let t_to_one = SymmetricalBasisPolynomial::<6, f64> {
1140            coeffs: [0., 1., 0., 0., 0., 0.],
1141        };
1142        let t_squared = SymmetricalBasisPolynomial::<6, f64> {
1143            coeffs: [0., 1., -1., -1., 0., 0.],
1144        };
1145        assert_eq!(
1146            t_to_one
1147                .differentiate()
1148                .expect("this can be differentiated without issue")
1149                .coeffs,
1150            one.coeffs
1151        );
1152        let neg_one = SymmetricalBasisPolynomial::<6, f64> {
1153            coeffs: [-1., -1., 0., 0., 0., 0.],
1154        };
1155        let diff_0 = SymmetricalBasisPolynomial::<6, f64>::differentiate_single(0);
1156        assert_eq!(diff_0.coeffs, neg_one.coeffs);
1157        assert_eq!(
1158            SymmetricalBasisPolynomial::<6, f64>::differentiate_single(1).coeffs,
1159            one.coeffs
1160        );
1161        // derivative of (1-t)*s=t-2t^2+t^3
1162        // is 1 - 4t + 3t^2
1163        let single_term_2 = SymmetricalBasisPolynomial::<6, f64> {
1164            coeffs: [0., 0., 1., 0., 0., 0.],
1165        };
1166        let t_cubed = SymmetricalBasisPolynomial::<6, f64> {
1167            coeffs: [0., 1., -1., -2., 0., 0.],
1168        };
1169        let t_to_one = SymmetricalBasisPolynomial::<6, f64> {
1170            coeffs: [0., 1., 0., 0., 0., 0.],
1171        };
1172        let expected = t_to_one - t_squared * 2. + t_cubed;
1173        assert_eq!(single_term_2.coeffs, expected.coeffs);
1174        let t_to_one = SymmetricalBasisPolynomial::<6, f64> {
1175            coeffs: [0., 1., 0., 0., 0., 0.],
1176        };
1177        let t_squared = SymmetricalBasisPolynomial::<6, f64> {
1178            coeffs: [0., 1., -1., -1., 0., 0.],
1179        };
1180        let expected: SymmetricalBasisPolynomial<6, f64> = one + t_to_one * (-4.) + t_squared * 3.;
1181        let diff_2 = SymmetricalBasisPolynomial::<6, f64>::differentiate_single(2);
1182        let pretty_diff_2 = diff_2.pretty_format("t", &|z: &f64| z.abs() < f64::EPSILON);
1183        let pretty_expected = expected.pretty_format("t", &|z: &f64| z.abs() < f64::EPSILON);
1184        assert_eq!(pretty_expected, pretty_diff_2);
1185        assert_eq!(diff_2.coeffs, expected.coeffs);
1186        // derivative of t*s=t^2-t^3
1187        // is 2t-3t^2
1188        let t_to_one = SymmetricalBasisPolynomial::<6, f64> {
1189            coeffs: [0., 1., 0., 0., 0., 0.],
1190        };
1191        let t_squared = SymmetricalBasisPolynomial::<6, f64> {
1192            coeffs: [0., 1., -1., -1., 0., 0.],
1193        };
1194        let expected: SymmetricalBasisPolynomial<6, f64> = t_to_one * 2. - t_squared * 3.;
1195        assert_eq!(
1196            SymmetricalBasisPolynomial::<6, f64>::differentiate_single(3).coeffs,
1197            expected.coeffs
1198        );
1199        // derivative of (1-t)*s*2=t^2-3t^3+3t^4-t^5
1200        // is 2t - 9t^2+12t^3-5t^4
1201        let t_to_one = SymmetricalBasisPolynomial::<6, f64> {
1202            coeffs: [0., 1., 0., 0., 0., 0.],
1203        };
1204        let t_squared = SymmetricalBasisPolynomial::<6, f64> {
1205            coeffs: [0., 1., -1., -1., 0., 0.],
1206        };
1207        let t_cubed = SymmetricalBasisPolynomial::<6, f64> {
1208            coeffs: [0., 1., -1., -2., 0., 0.],
1209        };
1210        let t_fourth = t_cubed
1211            .multiply_by_t(true, &|z| z.abs() < 0.000_001)
1212            .unwrap();
1213        let t_cubed = SymmetricalBasisPolynomial::<6, f64> {
1214            coeffs: [0., 1., -1., -2., 0., 0.],
1215        };
1216        let expected: SymmetricalBasisPolynomial<6, f64> =
1217            t_to_one * 2. - t_squared * 9. + t_cubed * 12. - t_fourth * 5.;
1218        assert_eq!(
1219            SymmetricalBasisPolynomial::<6, f64>::differentiate_single(4).coeffs,
1220            expected.coeffs
1221        );
1222        // derivative of t*s*2=t^3*(1-2t+t^2)=t^3-2*t^4+t^5
1223        // 3t^2 -8t^3 + 5t^4
1224        let t_squared = SymmetricalBasisPolynomial::<6, f64> {
1225            coeffs: [0., 1., -1., -1., 0., 0.],
1226        };
1227        let t_cubed = SymmetricalBasisPolynomial::<6, f64> {
1228            coeffs: [0., 1., -1., -2., 0., 0.],
1229        };
1230        let t_fourth = t_cubed
1231            .multiply_by_t(true, &|z| z.abs() < 0.000_001)
1232            .unwrap();
1233        assert_eq!(t_fourth.coeffs, [0., 1., -1., -3., 1., 1.]);
1234        let t_cubed = SymmetricalBasisPolynomial::<6, f64> {
1235            coeffs: [0., 1., -1., -2., 0., 0.],
1236        };
1237        let expected: SymmetricalBasisPolynomial<6, f64> =
1238            t_fourth * 5. - t_cubed * 8. + t_squared * 3.;
1239        assert_eq!(
1240            SymmetricalBasisPolynomial::<6, f64>::differentiate_single(5).coeffs,
1241            expected.coeffs
1242        );
1243    }
1244
1245    #[test]
1246    #[allow(clippy::float_cmp)]
1247    fn test_differentiate_single_nonhardcoded() {
1248        use super::SymmetricalBasisPolynomial;
1249        //use crate::generic_polynomial::Generic1DPoly;
1250        let diff_6 = SymmetricalBasisPolynomial::<10, f64>::differentiate_single_hardcoded(6);
1251        assert!(diff_6.is_none());
1252        let diff_6 = SymmetricalBasisPolynomial::<10, f64>::differentiate_single(6);
1253        let expected_diff_6 = SymmetricalBasisPolynomial::<10, f64> {
1254            coeffs: [0., 0., 0., 0., 3., 0., -7., -7., 0., 0.],
1255        };
1256        assert_eq!(diff_6.coeffs, expected_diff_6.coeffs);
1257    }
1258
1259    // different order of computation so the errors for accurately running tests
1260    // could be larger than machine epsilon for f64
1261    // things like non-associativity building up over many steps
1262    #[allow(dead_code)]
1263    const TEST_EPSILON: f64 = f64::EPSILON;
1264
1265    #[test]
1266    fn monomial_multiply_by_t() {
1267        use crate::generic_polynomial::{DegreeType, Generic1DPoly};
1268        use crate::my_symmetrical_basis_pair::SymmetricalBasisPolynomial;
1269        const HOW_MANY_SYM_BASIS: usize = 10;
1270        const DEGREE_HANDLEABLE: DegreeType = 9;
1271        const EXPECT_MESSAGE: &str = "For degrees <= 9, 10 symmetric basis coefficients are enough";
1272        const EXPECT_MESSAGE_2 : &str = "If degree+1 <= 9, then there should be no problem multiplying t^degree by t to get t^(degree+1)";
1273        let zero_float = |z: &f64| z.abs() < TEST_EPSILON;
1274        for degree in 0..DEGREE_HANDLEABLE + 5 {
1275            let in_sym_basis =
1276                SymmetricalBasisPolynomial::<HOW_MANY_SYM_BASIS, f64>::create_monomial(
1277                    degree,
1278                    &zero_float,
1279                    degree <= DEGREE_HANDLEABLE,
1280                );
1281            if degree > DEGREE_HANDLEABLE {
1282                assert!(in_sym_basis.is_err());
1283            } else {
1284                let real_in_sym_basis = in_sym_basis.expect(EXPECT_MESSAGE);
1285                #[allow(clippy::int_plus_one)]
1286                let after_mul_t = real_in_sym_basis
1287                    .clone()
1288                    .multiply_by_t(degree + 1 <= DEGREE_HANDLEABLE, &zero_float);
1289                #[allow(clippy::collapsible_if)]
1290                if after_mul_t.is_none() {
1291                    if degree + 1 > DEGREE_HANDLEABLE {
1292                        break;
1293                    }
1294                }
1295                let after_mul_t = after_mul_t.expect(EXPECT_MESSAGE_2);
1296                for t_point in [0., 0.2, 0.3564, 0.5335, 0.789, 0.999, 1.] {
1297                    let without_t_factor = real_in_sym_basis.evaluate_at(t_point);
1298                    let with_t_factor = after_mul_t.evaluate_at(t_point);
1299                    let diff = without_t_factor * t_point - with_t_factor;
1300                    assert!(
1301                        diff.abs() < TEST_EPSILON,
1302                        "{without_t_factor} {with_t_factor} {degree} {t_point}"
1303                    );
1304                }
1305            }
1306        }
1307    }
1308}
1309
1310mod test_more {
1311
1312    // different order of computation so the errors for accurately running tests
1313    // could be larger than machine epsilon for f64
1314    // things like non-associativity building up over many steps
1315    #[allow(dead_code)]
1316    const TEST_EPSILON: f64 = 1e-9;
1317
1318    #[test]
1319    fn monomials_match() {
1320        use crate::generic_polynomial::test_same_polynomial;
1321        use crate::generic_polynomial::{DegreeType, Generic1DPoly};
1322        use crate::my_symmetrical_basis_pair::SymmetricalBasisPolynomial;
1323        use crate::ordinary_polynomial::MonomialBasisPolynomial;
1324        const HOW_MANY_SYM_BASIS: usize = 9;
1325        const DEGREE_HANDLEABLE: DegreeType = 7;
1326        const EXPECT_MESSAGE: &str = "For degrees <= 7, 9 symmetric basis coefficients are enough, can't do 8 without the 10th, once have 10th then can do 8 and 9";
1327        let zero_float = |z: &f64| z.abs() < TEST_EPSILON;
1328        for degree in 0..DEGREE_HANDLEABLE + 5 {
1329            let in_ordinary =
1330                MonomialBasisPolynomial::<f64>::create_monomial(degree, &zero_float, true)
1331                    .expect("No out of const size for these");
1332            let in_sym_basis =
1333                SymmetricalBasisPolynomial::<HOW_MANY_SYM_BASIS, f64>::create_monomial(
1334                    degree,
1335                    &zero_float,
1336                    degree <= DEGREE_HANDLEABLE,
1337                );
1338            if degree > DEGREE_HANDLEABLE {
1339                assert!(in_sym_basis.is_err());
1340            } else {
1341                let real_in_sym_basis = in_sym_basis.expect(EXPECT_MESSAGE);
1342                test_same_polynomial(
1343                    &in_ordinary,
1344                    &real_in_sym_basis,
1345                    degree,
1346                    [0., 0.2, 0.3564, 0.5335, 0.789, 0.999, 1.],
1347                    &|&diff| (diff.abs() < TEST_EPSILON),
1348                );
1349            }
1350        }
1351    }
1352
1353    #[test]
1354    fn monomial_derivatives_match() {
1355        use crate::generic_polynomial::test_same_polynomial;
1356        use crate::generic_polynomial::{DegreeType, Generic1DPoly};
1357        use crate::my_symmetrical_basis_pair::SymmetricalBasisPolynomial;
1358        use crate::ordinary_polynomial::MonomialBasisPolynomial;
1359        const HOW_MANY_SYM_BASIS: usize = 10;
1360        const DEGREE_HANDLEABLE: DegreeType = 9;
1361        const EXPECT_MESSAGE: &str = "For degrees <= 9, 10 symmetric basis coefficients are enough";
1362        let zero_float = |z: &f64| z.abs() < TEST_EPSILON;
1363        for degree in 0..DEGREE_HANDLEABLE + 5 {
1364            let in_ordinary =
1365                MonomialBasisPolynomial::<f64>::create_monomial(degree, &zero_float, true)
1366                    .expect("No out of const size for these");
1367            let in_ordinary = in_ordinary
1368                .differentiate()
1369                .expect("this can be differentiated without issue");
1370            let in_sym_basis =
1371                SymmetricalBasisPolynomial::<HOW_MANY_SYM_BASIS, f64>::create_monomial(
1372                    degree,
1373                    &zero_float,
1374                    degree <= DEGREE_HANDLEABLE,
1375                );
1376            if degree > DEGREE_HANDLEABLE {
1377                assert!(in_sym_basis.is_err());
1378            } else {
1379                let real_in_sym_basis = in_sym_basis.expect(EXPECT_MESSAGE);
1380                let real_in_sym_basis = real_in_sym_basis
1381                    .differentiate()
1382                    .expect("this can be differentiated without issue");
1383                test_same_polynomial(
1384                    &in_ordinary,
1385                    &real_in_sym_basis,
1386                    degree,
1387                    [0., 0.2, 0.3564, 0.5335, 0.789, 0.999, 1.],
1388                    &|&diff| (diff.abs() < TEST_EPSILON),
1389                );
1390            }
1391        }
1392    }
1393}