1use crate::error::Error;
7use crate::matrix::{Mat, Mat3};
8
9#[derive(Debug, Clone)]
11pub struct PiecewiseLegendrePoly {
12 pub(crate) polyorder: usize,
14 pub(crate) xmin: f64,
16 pub(crate) xmax: f64,
18 pub(crate) knots: Vec<f64>,
20 pub(crate) delta_x: Vec<f64>,
22 pub(crate) data: Mat<f64>,
24 pub(crate) symm: i32,
26 pub(crate) l: i32,
36 pub(crate) xm: Vec<f64>,
38 pub(crate) inv_xs: Vec<f64>,
40 pub(crate) norms: Vec<f64>,
42}
43
44fn check_knots(knots: &[f64], nsegments: usize) -> Result<(), Error> {
47 if knots.len() != nsegments + 1 {
48 return Err(Error::InvalidParameter {
49 name: "knots",
50 value: format!("{} knots", knots.len()),
51 reason: format!(
52 "must have {} entries, one more than the segments of data",
53 nsegments + 1
54 ),
55 });
56 }
57 if let Some((i, k)) = knots.iter().enumerate().find(|(_, k)| !k.is_finite()) {
58 return Err(Error::InvalidParameter {
59 name: "knots",
60 value: format!("{k:?} at index {i}"),
61 reason: "must be finite".to_string(),
62 });
63 }
64 for i in 1..knots.len() {
65 let length = knots[i] - knots[i - 1];
66 if !(length > 0.0 && length.is_normal()) {
67 return Err(Error::InvalidParameter {
68 name: "knots",
69 value: format!("{:?} after {:?} at index {i}", knots[i], knots[i - 1]),
70 reason: "must be strictly increasing, with each segment length a normal double"
71 .to_string(),
72 });
73 }
74 }
75 Ok(())
76}
77
78fn check_delta_x(delta_x: &[f64], knots: &[f64]) -> Result<(), Error> {
89 let nsegments = knots.len() - 1;
90 if delta_x.len() != nsegments {
91 return Err(Error::InvalidParameter {
92 name: "delta_x",
93 value: format!("{} entries", delta_x.len()),
94 reason: format!("must have one entry per segment ({nsegments})"),
95 });
96 }
97 for (i, &d) in delta_x.iter().enumerate() {
98 let expected = knots[i + 1] - knots[i];
99 let rounding = 8.0 * f64::EPSILON * knots[i].abs().max(knots[i + 1].abs());
100 let tolerance = (1e-10 * expected.abs()).max(rounding);
101 if !((d - expected).abs() <= tolerance) {
102 return Err(Error::InvalidParameter {
103 name: "delta_x",
104 value: format!("{d:?} at index {i}"),
105 reason: format!(
106 "must equal the knot spacing {expected:?} to a relative 1e-10, or to 8 machine epsilons times the magnitude of the knots"
107 ),
108 });
109 }
110 }
111 Ok(())
112}
113
114impl PiecewiseLegendrePoly {
115 pub fn new(
134 data: Mat<f64>,
135 knots: Vec<f64>,
136 l: i32,
137 delta_x: Option<Vec<f64>>,
138 symm: i32,
139 ) -> Result<Self, Error> {
140 let polyorder = data.shape().0;
141 let nsegments = data.shape().1;
142 if polyorder == 0 || nsegments == 0 {
143 return Err(Error::EmptyInput { name: "data" });
144 }
145 check_knots(&knots, nsegments)?;
146
147 let delta_x =
149 delta_x.unwrap_or_else(|| (1..knots.len()).map(|i| knots[i] - knots[i - 1]).collect());
150 check_delta_x(&delta_x, &knots)?;
151 if !matches!(symm, -1..=1) {
152 return Err(Error::InvalidParameter {
153 name: "symm",
154 value: symm.to_string(),
155 reason: "must be -1, 0 or 1".to_string(),
156 });
157 }
158
159 let xm: Vec<f64> = (0..nsegments)
161 .map(|i| 0.5 * (knots[i] + knots[i + 1]))
162 .collect();
163
164 let inv_xs: Vec<f64> = delta_x.iter().map(|&dx| 2.0 / dx).collect();
166
167 let norms: Vec<f64> = inv_xs.iter().map(|&inv_x| inv_x.sqrt()).collect();
169
170 Ok(Self {
171 polyorder,
172 xmin: knots[0],
173 xmax: knots[knots.len() - 1],
174 knots,
175 delta_x,
176 data,
177 symm,
178 l,
179 xm,
180 inv_xs,
181 norms,
182 })
183 }
184
185 pub(crate) fn with_data(&self, new_data: Mat<f64>) -> Self {
190 Self {
191 data: new_data,
192 ..self.clone()
193 }
194 }
195
196 pub fn symm(&self) -> i32 {
198 self.symm
199 }
200
201 pub(crate) fn negated(&self) -> Self {
206 self.with_data(Mat::<f64>::from_fn(self.data.dims(), |idx| -self.data[idx]))
207 }
208
209 pub fn rescale_domain(
228 &self,
229 new_knots: Vec<f64>,
230 new_delta_x: Option<Vec<f64>>,
231 new_symm: Option<i32>,
232 ) -> Result<Self, Error> {
233 Self::new(
234 self.data.clone(),
235 new_knots,
236 self.l,
237 new_delta_x,
238 new_symm.unwrap_or(self.symm),
239 )
240 }
241
242 pub fn scale_data(&self, factor: f64) -> Self {
255 Self::with_data(
256 self,
257 Mat::<f64>::from_fn(self.data.dims(), |idx| self.data[idx] * factor),
258 )
259 }
260
261 fn check_in_domain(&self, name: &'static str, x: f64) -> Result<(), Error> {
263 if x >= self.xmin && x <= self.xmax {
264 Ok(())
265 } else {
266 Err(Error::OutOfDomain {
267 name,
268 value: x,
269 domain: (self.xmin, self.xmax),
270 })
271 }
272 }
273
274 pub fn evaluate(&self, x: f64) -> f64 {
280 self.try_evaluate(x).unwrap_or_else(|e| panic!("{e}"))
281 }
282
283 pub fn try_evaluate(&self, x: f64) -> Result<f64, Error> {
289 self.check_in_domain("x", x)?;
290 Ok(self.evaluate_in_domain(x))
291 }
292
293 fn evaluate_in_domain(&self, x: f64) -> f64 {
295 let (i, x_tilde) = self.split_in_domain(x);
296 let coeffs: Vec<f64> = (0..self.data.shape().0)
298 .map(|row| self.data[[row, i]])
299 .collect();
300 let value = self.evaluate_legendre_polynomial(x_tilde, &coeffs);
301 value * self.norms[i]
302 }
303
304 pub fn evaluate_many(&self, xs: &[f64]) -> Vec<f64> {
311 self.try_evaluate_many(xs).unwrap_or_else(|e| panic!("{e}"))
312 }
313
314 pub fn try_evaluate_many(&self, xs: &[f64]) -> Result<Vec<f64>, Error> {
321 for &x in xs {
322 self.check_in_domain("xs", x)?;
323 }
324 Ok(xs.iter().map(|&x| self.evaluate_in_domain(x)).collect())
325 }
326
327 pub fn split(&self, x: f64) -> (usize, f64) {
333 self.try_split(x).unwrap_or_else(|e| panic!("{e}"))
334 }
335
336 pub fn try_split(&self, x: f64) -> Result<(usize, f64), Error> {
342 self.check_in_domain("x", x)?;
343 Ok(self.split_in_domain(x))
344 }
345
346 fn split_in_domain(&self, x: f64) -> (usize, f64) {
348 for i in 0..self.knots.len() - 1 {
350 if x >= self.knots[i] && x <= self.knots[i + 1] {
351 let x_tilde = 2.0 * (x - self.xm[i]) / self.delta_x[i];
353 return (i, x_tilde);
354 }
355 }
356
357 let last_idx = self.knots.len() - 2;
359 let x_tilde = 2.0 * (x - self.xm[last_idx]) / self.delta_x[last_idx];
360 (last_idx, x_tilde)
361 }
362
363 pub fn evaluate_legendre_polynomial(&self, x: f64, coeffs: &[f64]) -> f64 {
365 if coeffs.is_empty() {
366 return 0.0;
367 }
368
369 let mut result = 0.0;
370 let mut p_prev = 1.0; let mut p_curr = x; if !coeffs.is_empty() {
375 result += coeffs[0] * p_prev;
376 }
377 if coeffs.len() > 1 {
378 result += coeffs[1] * p_curr;
379 }
380
381 for n in 1..coeffs.len() - 1 {
383 let p_next =
384 ((2.0 * (n as f64) + 1.0) * x * p_curr - (n as f64) * p_prev) / ((n + 1) as f64);
385 result += coeffs[n + 1] * p_next;
386 p_prev = p_curr;
387 p_curr = p_next;
388 }
389
390 result
391 }
392
393 pub fn deriv(&self, n: usize) -> Self {
398 if n == 0 {
399 return self.clone();
400 }
401
402 let mut ddata = self.data.clone();
404 for _ in 0..n {
405 ddata = self.compute_derivative_coefficients(&ddata);
406 }
407
408 let ddata_shape = *ddata.shape();
410 for i in 0..ddata_shape.1 {
411 let inv_x_power = self.inv_xs[i].powi(n as i32);
412 for j in 0..ddata_shape.0 {
413 ddata[[j, i]] *= inv_x_power;
414 }
415 }
416
417 let new_symm = if n % 2 == 0 { self.symm } else { -self.symm };
419
420 Self {
421 polyorder: ddata.shape().0,
422 data: ddata,
423 symm: new_symm,
424 ..self.clone()
425 }
426 }
427
428 fn compute_derivative_coefficients(&self, coeffs: &Mat<f64>) -> Mat<f64> {
430 let mut c = coeffs.clone();
431 let c_shape = *c.shape();
432 let mut n = c_shape.0;
433
434 if n <= 1 {
436 return Mat::<f64>::from_elem([1, c.shape().1], 0.0);
437 }
438
439 n -= 1;
440 let mut der = Mat::<f64>::from_elem([n, c.shape().1], 0.0);
441
442 for j in (2..=n).rev() {
444 for col in 0..c_shape.1 {
446 der[[j - 1, col]] = (2.0 * (j as f64) - 1.0) * c[[j, col]];
447 }
448 for col in 0..c_shape.1 {
450 c[[j - 2, col]] += c[[j, col]];
451 }
452 }
453
454 if n > 1 {
456 for col in 0..c_shape.1 {
457 der[[1, col]] = 3.0 * c[[2, col]];
458 }
459 }
460
461 for col in 0..c_shape.1 {
463 der[[0, col]] = c[[1, col]];
464 }
465
466 der
467 }
468
469 pub fn derivs(&self, x: f64) -> Vec<f64> {
477 self.try_derivs(x).unwrap_or_else(|e| panic!("{e}"))
478 }
479
480 pub fn try_derivs(&self, x: f64) -> Result<Vec<f64>, Error> {
486 self.check_in_domain("x", x)?;
487 let mut results = Vec::new();
488
489 for n in 0..self.polyorder {
492 let deriv_poly = self.deriv(n);
493 results.push(deriv_poly.evaluate_in_domain(x));
494 }
495
496 Ok(results)
497 }
498
499 pub fn overlap<F>(&self, f: F) -> f64
501 where
502 F: Fn(f64) -> f64,
503 {
504 let mut integral = 0.0;
505
506 for i in 0..self.knots.len() - 1 {
507 let segment_integral =
508 self.gauss_legendre_quadrature(self.knots[i], self.knots[i + 1], |x| {
509 self.evaluate(x) * f(x)
510 });
511 integral += segment_integral;
512 }
513
514 integral
515 }
516
517 fn gauss_legendre_quadrature<F>(&self, a: f64, b: f64, f: F) -> f64
519 where
520 F: Fn(f64) -> f64,
521 {
522 const XG: [f64; 5] = [
524 -0.906179845938664,
525 -0.538469310105683,
526 0.0,
527 0.538469310105683,
528 0.906179845938664,
529 ];
530 const WG: [f64; 5] = [
531 0.236926885056189,
532 0.478628670499366,
533 0.568888888888889,
534 0.478628670499366,
535 0.236926885056189,
536 ];
537
538 let c1 = (b - a) / 2.0;
539 let c2 = (b + a) / 2.0;
540
541 let mut integral = 0.0;
542 for j in 0..5 {
543 let x = c1 * XG[j] + c2;
544 integral += WG[j] * f(x);
545 }
546
547 integral * c1
548 }
549
550 pub fn roots(&self) -> Vec<f64> {
552 let xmid = (self.xmax + self.xmin) / 2.0;
553
554 let grid = if self.symm != 0 {
558 let nsegments = self.knots.len() - 1;
559 let mid_idx = nsegments / 2;
560 if (self.knots[mid_idx] - xmid).abs() < 1e-15 {
561 self.knots[mid_idx..].to_vec()
562 } else {
563 let mut g = vec![xmid];
564 g.extend(self.knots.iter().filter(|&&x| x > xmid));
565 g
566 }
567 } else {
568 self.knots.clone()
569 };
570
571 let refined_grid = self.refine_grid(&grid, 4);
572 let roots_half = self.find_all_roots(&refined_grid);
573
574 if self.symm == 1 {
575 let mut all_roots: Vec<f64> = roots_half
577 .iter()
578 .rev()
579 .map(|&r| (self.xmax + self.xmin) - r)
580 .collect();
581 all_roots.extend_from_slice(&roots_half);
582 all_roots
583 } else if self.symm == -1 {
584 let mut right = roots_half;
586 if !right.is_empty() {
587 let f_mid = self.evaluate(xmid);
590 let f_deriv_mid = self.deriv(1).evaluate(xmid);
591 if (right[0] - xmid).abs() < 1e-13 || f_mid * f_deriv_mid < 0.0 {
592 right.remove(0);
593 }
594 }
595 let mut all_roots: Vec<f64> = right
596 .iter()
597 .rev()
598 .map(|&r| (self.xmax + self.xmin) - r)
599 .collect();
600 all_roots.push(xmid);
601 all_roots.extend_from_slice(&right);
602 all_roots
603 } else {
604 let full_grid = self.refine_grid(&self.knots, 4);
606 self.find_all_roots(&full_grid)
607 }
608 }
609
610 fn refine_grid(&self, grid: &[f64], alpha: usize) -> Vec<f64> {
612 let mut refined = Vec::new();
613
614 for i in 0..grid.len() - 1 {
615 let start = grid[i];
616 let step = (grid[i + 1] - grid[i]) / (alpha as f64);
617 for j in 0..alpha {
618 refined.push(start + (j as f64) * step);
619 }
620 }
621 refined.push(grid[grid.len() - 1]);
622 refined
623 }
624
625 fn find_all_roots(&self, xgrid: &[f64]) -> Vec<f64> {
627 if xgrid.is_empty() {
628 return Vec::new();
629 }
630
631 let fx: Vec<f64> = xgrid.iter().map(|&x| self.evaluate(x)).collect();
633
634 let mut x_hit = Vec::new();
636 for i in 0..fx.len() {
637 if fx[i] == 0.0 {
638 x_hit.push(xgrid[i]);
639 }
640 }
641
642 let mut sign_change = Vec::new();
644 for i in 0..fx.len() - 1 {
645 let has_sign_change = fx[i].signum() != fx[i + 1].signum();
646 let not_hit = fx[i] != 0.0 && fx[i + 1] != 0.0;
647 let sc = has_sign_change && not_hit;
648 sign_change.push(sc);
649 }
650
651 if sign_change.iter().all(|&sc| !sc) {
653 x_hit.sort_by(|a, b| a.partial_cmp(b).unwrap());
654 return x_hit;
655 }
656
657 let mut a_intervals = Vec::new();
659 let mut b_intervals = Vec::new();
660 let mut fa_values = Vec::new();
661
662 for i in 0..sign_change.len() {
663 if sign_change[i] {
664 a_intervals.push(xgrid[i]);
665 b_intervals.push(xgrid[i + 1]);
666 fa_values.push(fx[i]);
667 }
668 }
669
670 let max_elm = xgrid.iter().map(|&x| x.abs()).fold(0.0, f64::max);
672 let epsilon_x = f64::EPSILON * max_elm;
673
674 for i in 0..a_intervals.len() {
676 let root = self.bisect(a_intervals[i], b_intervals[i], fa_values[i], epsilon_x);
677 x_hit.push(root);
678 }
679
680 x_hit.sort_by(|a, b| a.partial_cmp(b).unwrap());
682 x_hit
683 }
684
685 fn bisect(&self, a: f64, b: f64, fa: f64, eps: f64) -> f64 {
687 let mut a = a;
688 let mut b = b;
689 let mut fa = fa;
690
691 loop {
692 let mid = (a + b) / 2.0;
693 if self.close_enough(a, mid, eps) {
694 return mid;
695 }
696
697 let fmid = self.evaluate(mid);
698 if fa.signum() != fmid.signum() {
699 b = mid;
700 } else {
701 a = mid;
702 fa = fmid;
703 }
704 }
705 }
706
707 fn close_enough(&self, a: f64, b: f64, eps: f64) -> bool {
709 (a - b).abs() <= eps
710 }
711
712 pub fn get_xmin(&self) -> f64 {
714 self.xmin
715 }
716 pub fn get_xmax(&self) -> f64 {
717 self.xmax
718 }
719 pub fn get_l(&self) -> i32 {
720 self.l
721 }
722 pub fn get_domain(&self) -> (f64, f64) {
723 (self.xmin, self.xmax)
724 }
725 pub fn get_knots(&self) -> &[f64] {
726 &self.knots
727 }
728 pub fn get_delta_x(&self) -> &[f64] {
729 &self.delta_x
730 }
731 pub fn get_symm(&self) -> i32 {
732 self.symm
733 }
734 pub fn get_data(&self) -> &Mat<f64> {
735 &self.data
736 }
737 pub fn get_norms(&self) -> &[f64] {
738 &self.norms
739 }
740 pub fn get_polyorder(&self) -> usize {
741 self.polyorder
742 }
743}
744
745#[derive(Debug, Clone)]
747pub struct PiecewiseLegendrePolyVector {
748 pub(crate) polyvec: Vec<PiecewiseLegendrePoly>,
750}
751
752impl PiecewiseLegendrePolyVector {
753 pub fn new(polyvec: Vec<PiecewiseLegendrePoly>) -> Result<Self, Error> {
762 let Some(first) = polyvec.first() else {
763 return Err(Error::EmptyInput { name: "polyvec" });
764 };
765 if let Some(i) = polyvec
766 .iter()
767 .position(|p| p.knots != first.knots || p.data.shape() != first.data.shape())
768 {
769 return Err(Error::InvalidParameter {
770 name: "polyvec",
771 value: format!("polynomial {i}"),
772 reason: "must have the knots and the data shape of polynomial 0".to_string(),
773 });
774 }
775 Ok(Self { polyvec })
776 }
777
778 pub(crate) fn from_polys_unchecked(polyvec: Vec<PiecewiseLegendrePoly>) -> Self {
785 Self { polyvec }
786 }
787
788 pub fn get_polys(&self) -> &[PiecewiseLegendrePoly] {
790 &self.polyvec
791 }
792
793 pub fn from_3d_data(
805 data3d: Mat3<f64>,
806 knots: Vec<f64>,
807 symm: Option<Vec<i32>>,
808 ) -> Result<Self, Error> {
809 let npolys = data3d.shape().2;
810 if npolys == 0 {
811 return Err(Error::EmptyInput { name: "data3d" });
812 }
813 let mut polyvec = Vec::with_capacity(npolys);
814
815 if let Some(ref symm_vec) = symm {
816 if symm_vec.len() != npolys {
817 return Err(Error::InvalidParameter {
818 name: "symm",
819 value: format!("{} entries", symm_vec.len()),
820 reason: format!("must have one entry per polynomial ({npolys})"),
821 });
822 }
823 }
824
825 let delta_x: Vec<f64> = (1..knots.len()).map(|i| knots[i] - knots[i - 1]).collect();
827
828 for i in 0..npolys {
829 let data3d_shape = data3d.shape();
831 let mut data = Mat::<f64>::from_elem([data3d_shape.0, data3d_shape.1], 0.0);
832 for j in 0..data3d_shape.0 {
833 for k in 0..data3d_shape.1 {
834 data[[j, k]] = data3d[[j, k, i]];
835 }
836 }
837
838 let poly = PiecewiseLegendrePoly::new(
839 data,
840 knots.clone(),
841 i as i32,
842 Some(delta_x.clone()),
843 symm.as_ref().map_or(0, |s| s[i]),
844 )?;
845
846 polyvec.push(poly);
847 }
848
849 Ok(Self { polyvec })
850 }
851
852 pub fn size(&self) -> usize {
854 self.polyvec.len()
855 }
856
857 pub fn rescale_domain(
878 &self,
879 new_knots: Vec<f64>,
880 new_delta_x: Option<Vec<f64>>,
881 new_symm: Option<Vec<i32>>,
882 ) -> Result<Self, Error> {
883 if let Some(symm) = &new_symm {
884 if symm.len() != self.polyvec.len() {
885 return Err(Error::InvalidParameter {
886 name: "new_symm",
887 value: format!("{} entries", symm.len()),
888 reason: format!(
889 "must have one entry per polynomial ({})",
890 self.polyvec.len()
891 ),
892 });
893 }
894 }
895 let polyvec = self
896 .polyvec
897 .iter()
898 .enumerate()
899 .map(|(i, poly)| {
900 let symm = new_symm.as_ref().map(|s| s[i]);
901 poly.rescale_domain(new_knots.clone(), new_delta_x.clone(), symm)
902 })
903 .collect::<Result<_, _>>()?;
904 Ok(Self { polyvec })
905 }
906
907 pub fn scale_data(&self, factor: f64) -> Self {
919 let polyvec = self
920 .polyvec
921 .iter()
922 .map(|poly| poly.scale_data(factor))
923 .collect();
924
925 Self { polyvec }
926 }
927
928 pub fn get(&self, index: usize) -> Option<&PiecewiseLegendrePoly> {
930 self.polyvec.get(index)
931 }
932
933 #[deprecated(
935 note = "PiecewiseLegendrePolyVector is designed to be immutable. Use get() and create new instances for modifications."
936 )]
937 pub fn get_mut(&mut self, index: usize) -> Option<&mut PiecewiseLegendrePoly> {
938 self.polyvec.get_mut(index)
939 }
940
941 pub fn slice_single(&self, index: usize) -> Option<Self> {
943 self.polyvec.get(index).map(|poly| Self {
944 polyvec: vec![poly.clone()],
945 })
946 }
947
948 pub fn slice_multi(&self, indices: &[usize]) -> Result<Self, Error> {
956 if indices.is_empty() {
957 return Err(Error::EmptyInput { name: "indices" });
958 }
959 let len = self.polyvec.len();
960 let mut seen = vec![false; len];
961 for &idx in indices {
962 if idx >= len {
963 return Err(Error::InvalidParameter {
964 name: "indices",
965 value: format!("{idx}"),
966 reason: format!("must be less than the size {len}"),
967 });
968 }
969 if seen[idx] {
970 return Err(Error::InvalidParameter {
971 name: "indices",
972 value: format!("{idx}"),
973 reason: "must not repeat".to_string(),
974 });
975 }
976 seen[idx] = true;
977 }
978
979 let new_polyvec: Vec<_> = indices
980 .iter()
981 .map(|&idx| self.polyvec[idx].clone())
982 .collect();
983
984 Ok(Self {
985 polyvec: new_polyvec,
986 })
987 }
988
989 pub fn evaluate_at(&self, x: f64) -> Vec<f64> {
996 self.try_evaluate_at(x).unwrap_or_else(|e| panic!("{e}"))
997 }
998
999 pub fn try_evaluate_at(&self, x: f64) -> Result<Vec<f64>, Error> {
1006 self.polyvec
1007 .iter()
1008 .map(|poly| poly.try_evaluate(x))
1009 .collect()
1010 }
1011
1012 pub fn evaluate_at_many(&self, xs: &[f64]) -> Mat<f64> {
1021 self.try_evaluate_at_many(xs)
1022 .unwrap_or_else(|e| panic!("{e}"))
1023 }
1024
1025 pub fn try_evaluate_at_many(&self, xs: &[f64]) -> Result<Mat<f64>, Error> {
1033 if let Some(first) = self.polyvec.first() {
1036 for &x in xs {
1037 first.check_in_domain("xs", x)?;
1038 }
1039 }
1040 let n_funcs = self.polyvec.len();
1041 let n_points = xs.len();
1042 let mut results = Mat::<f64>::from_elem([n_funcs, n_points], 0.0);
1043
1044 for (i, poly) in self.polyvec.iter().enumerate() {
1045 for (j, &x) in xs.iter().enumerate() {
1046 results[[i, j]] = poly.evaluate_in_domain(x);
1047 }
1048 }
1049
1050 Ok(results)
1051 }
1052
1053 pub fn xmin(&self) -> f64 {
1055 if self.polyvec.is_empty() {
1056 panic!("Cannot get xmin from empty PiecewiseLegendrePolyVector");
1057 }
1058 self.polyvec[0].xmin
1059 }
1060
1061 pub fn xmax(&self) -> f64 {
1062 if self.polyvec.is_empty() {
1063 panic!("Cannot get xmax from empty PiecewiseLegendrePolyVector");
1064 }
1065 self.polyvec[0].xmax
1066 }
1067
1068 pub fn get_knots(&self, tolerance: Option<f64>) -> Vec<f64> {
1069 if self.polyvec.is_empty() {
1070 panic!("Cannot get knots from empty PiecewiseLegendrePolyVector");
1071 }
1072 const DEFAULT_TOLERANCE: f64 = 1e-10;
1073 let tolerance = tolerance.unwrap_or(DEFAULT_TOLERANCE);
1074
1075 let mut all_knots = Vec::new();
1077 for poly in &self.polyvec {
1078 for &knot in &poly.knots {
1079 all_knots.push(knot);
1080 }
1081 }
1082
1083 {
1085 all_knots.sort_by(|a, b| a.partial_cmp(b).unwrap());
1086 all_knots.dedup_by(|a, b| (*a - *b).abs() < tolerance);
1087 }
1088 all_knots
1089 }
1090
1091 pub fn get_delta_x(&self) -> Vec<f64> {
1092 if self.polyvec.is_empty() {
1093 panic!("Cannot get delta_x from empty PiecewiseLegendrePolyVector");
1094 }
1095 self.polyvec[0].delta_x.clone()
1096 }
1097
1098 pub fn get_polyorder(&self) -> usize {
1099 if self.polyvec.is_empty() {
1100 panic!("Cannot get polyorder from empty PiecewiseLegendrePolyVector");
1101 }
1102 self.polyvec[0].polyorder
1103 }
1104
1105 pub fn get_norms(&self) -> &[f64] {
1106 if self.polyvec.is_empty() {
1107 panic!("Cannot get norms from empty PiecewiseLegendrePolyVector");
1108 }
1109 &self.polyvec[0].norms
1110 }
1111
1112 pub fn get_symm(&self) -> Vec<i32> {
1113 if self.polyvec.is_empty() {
1114 panic!("Cannot get symm from empty PiecewiseLegendrePolyVector");
1115 }
1116 self.polyvec.iter().map(|poly| poly.symm).collect()
1117 }
1118
1119 pub fn get_data(&self) -> Mat3<f64> {
1121 if self.polyvec.is_empty() {
1122 panic!("Cannot get data from empty PiecewiseLegendrePolyVector");
1123 }
1124
1125 let nsegments = self.polyvec[0].data.shape().1;
1126 let polyorder = self.polyvec[0].polyorder;
1127 let npolys = self.polyvec.len();
1128
1129 let mut data = Mat3::<f64>::from_elem([nsegments, polyorder, npolys], 0.0);
1130
1131 for (poly_idx, poly) in self.polyvec.iter().enumerate() {
1132 for segment in 0..nsegments {
1133 for degree in 0..polyorder {
1134 data[[segment, degree, poly_idx]] = poly.data[[degree, segment]];
1135 }
1136 }
1137 }
1138
1139 data
1140 }
1141
1142 pub fn roots(&self, tolerance: Option<f64>) -> Vec<f64> {
1144 if self.polyvec.is_empty() {
1145 panic!("Cannot get roots from empty PiecewiseLegendrePolyVector");
1146 }
1147 const DEFAULT_TOLERANCE: f64 = 1e-10;
1148 let tolerance = tolerance.unwrap_or(DEFAULT_TOLERANCE);
1149 let mut all_roots = Vec::new();
1150
1151 for poly in &self.polyvec {
1152 let poly_roots = poly.roots();
1153 for root in poly_roots {
1154 all_roots.push(root);
1155 }
1156 }
1157
1158 {
1160 all_roots.sort_by(|a, b| b.partial_cmp(a).unwrap());
1161 all_roots.dedup_by(|a, b| (*a - *b).abs() < tolerance);
1162 }
1163 all_roots
1164 }
1165
1166 pub fn last(&self) -> &PiecewiseLegendrePoly {
1170 self.polyvec
1171 .last()
1172 .expect("Cannot get last from empty PiecewiseLegendrePolyVector")
1173 }
1174
1175 pub fn nroots(&self, tolerance: Option<f64>) -> usize {
1177 if self.polyvec.is_empty() {
1178 panic!("Cannot get nroots from empty PiecewiseLegendrePolyVector");
1179 }
1180 self.roots(tolerance).len()
1181 }
1182}
1183
1184impl std::ops::Index<usize> for PiecewiseLegendrePolyVector {
1185 type Output = PiecewiseLegendrePoly;
1186
1187 fn index(&self, index: usize) -> &Self::Output {
1188 &self.polyvec[index]
1189 }
1190}
1191
1192pub(crate) fn default_sampling_points(
1215 u: &PiecewiseLegendrePolyVector,
1216 name: &'static str,
1217 l: usize,
1218) -> Result<Vec<f64>, Error> {
1219 if (u.xmin() - (-1.0)).abs() > 1e-10 || (u.xmax() - 1.0).abs() > 1e-10 {
1222 return Err(Error::InvalidParameter {
1223 name,
1224 value: format!("functions on [{:?}, {:?}]", u.xmin(), u.xmax()),
1225 reason: "must be the unscaled functions of an SVE, on [-1, 1]".to_string(),
1226 });
1227 }
1228
1229 let x0 = if l < u.polyvec.len() {
1230 u[l].roots()
1232 } else {
1233 let poly = u.last();
1236 let poly_deriv = poly.deriv(1);
1237 let maxima = poly_deriv.roots();
1238
1239 let (Some(&first), Some(&last)) = (maxima.first(), maxima.last()) else {
1242 return Err(Error::NotSupported {
1243 what: format!(
1244 "default sampling points for {l} basis functions: the last singular \
1245 function (l = {}) has no extrema to stand in for the roots of the \
1246 missing function l = {l} (an SVE truncated to too few functions)",
1247 poly.l
1248 ),
1249 });
1250 };
1251
1252 let left = (first + poly.xmin) / 2.0;
1254
1255 let right = (last + poly.xmax) / 2.0;
1257
1258 let mut x0_vec = Vec::with_capacity(maxima.len() + 2);
1263 x0_vec.push(left);
1264 x0_vec.extend_from_slice(&maxima);
1265 x0_vec.push(right);
1266 x0_vec
1267 };
1268
1269 if x0.len() != l {
1271 debug_warn!(
1272 "Expecting to get {} sampling points for corresponding basis function, \
1273 instead got {}. This may happen if not enough precision is left in the polynomial.",
1274 l,
1275 x0.len()
1276 );
1277 }
1278
1279 Ok(x0)
1280}
1281
1282#[cfg(test)]
1289#[path = "poly_tests.rs"]
1290mod poly_tests;