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#[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 #[must_use]
40 pub const fn polynomial_degree_bound() -> usize {
41 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 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 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 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 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 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 let s_power = n >> 1;
153 #[allow(clippy::cast_possible_truncation)]
154 let s_power_t: T = ((n >> 1) as SmallIntegers).into();
155 let where_one_minus_t_s_k = s_power << 1;
157 let mut answer = Self::create_zero_poly();
158 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 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 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 Err((0, 2))
213 }
214 (true, false) => {
215 Ok(2)
217 }
218 (false, true) => {
219 Ok(2)
221 }
222 (false, false) => {
223 Err((1, 2))
229 }
230 }
231 }
232
233 #[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 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 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 = answer
473 .multiply_by_t(surely_fits, zero_pred)
474 .ok_or(MonomialError::DesiredMonomialNotInSpace(cur_power + 1))?;
475 }
477 Ok(answer)
478 }
479
480 fn evaluate_at_many<const POINT_COUNT: usize>(&self, ts: [T; POINT_COUNT]) -> [T; POINT_COUNT] {
481 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 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 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 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 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 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 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 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 let mut only_zero = -constant_term;
749 only_zero /= linear_coeff;
750 Ok(vec![(only_zero, 0)])
751 }
752 Some(2) => {
753 let constant_term = self.evaluate_at_zero();
757 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 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 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 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 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 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 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 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 #[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 #[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}