From 66f26b03835adcf2df76f5e4ef3c826e797b0efb Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 4 Jun 2024 16:29:46 +1000 Subject: [PATCH 01/93] Add example on multiple roots using interpolation --- .vscode/settings.json | 1 + russell_lab/Cargo.toml | 2 +- .../examples/algo_interp_multiple_roots.rs | 134 ++++++++++++++++++ russell_lab/src/algo/interp_lagrange.rs | 6 + 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 russell_lab/examples/algo_interp_multiple_roots.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index f86097da..073350c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -93,6 +93,7 @@ "msun", "nelt", "Nørsett", + "nroot", "nstage", "nstep", "odyad", diff --git a/russell_lab/Cargo.toml b/russell_lab/Cargo.toml index 3890bfd4..91da8d33 100644 --- a/russell_lab/Cargo.toml +++ b/russell_lab/Cargo.toml @@ -21,7 +21,7 @@ serde = { version = "1.0", features = ["derive"] } [dev-dependencies] criterion = "0.5" -plotpy = "0.6" +plotpy = "0.7" serde_json = "1.0" serial_test = "3.0" diff --git a/russell_lab/examples/algo_interp_multiple_roots.rs b/russell_lab/examples/algo_interp_multiple_roots.rs new file mode 100644 index 00000000..6a2ac9c2 --- /dev/null +++ b/russell_lab/examples/algo_interp_multiple_roots.rs @@ -0,0 +1,134 @@ +use plotpy::{Curve, Legend, Plot}; +use russell_lab::*; + +fn main() -> Result<(), StrError> { + // function + let f = |x| x * x - 1.0; + let (xa, xb) = (-4.0, 4.0); + + // interpolant + let degree = 2; + let npoint = degree + 1; + let mut params = InterpParams::new(); + params.no_eta_normalization = true; + let interp = InterpLagrange::new(degree, Some(params)).unwrap(); + + // compute data points + let mut uu = Vector::new(npoint); + let yy = interp.get_points(); + for (i, y) in yy.into_iter().enumerate() { + let x = (xb + xa + (xb - xa) * y) / 2.0; + uu[i] = f(x); + } + println!("U = \n{}", uu); + + // interpolation + let lambda = interp.get_lambda(); + let p_interp = |x| { + let y = (2.0 * x - xb - xa) / (xb - xa); + let mut prod = 1.0; + for i in 0..npoint { + prod *= y - yy[i]; + } + let mut sum = 0.0; + for i in 0..npoint { + let wi = lambda[i]; + sum += (wi * uu[i]) / (y - yy[i]); + } + prod * sum + }; + + // companion matrix + let np = npoint; + let na = 1 + np; + let mut aa = Matrix::new(na, na); + let mut bb = Matrix::new(na, na); + for k in 0..np { + let wk = lambda[k]; + aa.set(0, 1 + k, -uu[k]); + aa.set(1 + k, 0, wk); + aa.set(1 + k, 1 + k, yy[k]); + bb.set(1 + k, 1 + k, 1.0); + } + println!("A =\n{:.3}", aa); + println!("B =\n{:.3}", bb); + + // interpolation using the companion matrix + let mut cc = Matrix::new(na, na); + let mut cc_inv = Matrix::new(na, na); + let mut p_companion = |x, a_mat, b_mat| { + // C := y B - A + let y = (2.0 * x - xb - xa) / (xb - xa); + mat_add(&mut cc, y, b_mat, -1.0, a_mat).unwrap(); + let det = mat_inverse(&mut cc_inv, &cc).unwrap(); + det + }; + + // generalized eigenvalues + let mut alpha_real = Vector::new(na); + let mut alpha_imag = Vector::new(na); + let mut beta = Vector::new(na); + let mut v = Matrix::new(na, na); + mat_gen_eigen(&mut alpha_real, &mut alpha_imag, &mut beta, &mut v, &mut aa, &mut bb)?; + + // print the results + println!("Re(α) =\n{}", alpha_real); + println!("Im(α) =\n{}", alpha_imag); + println!("β =\n{}", beta); + // println!("v =\n{:.2}", v); + + // roots = real eigenvalues + let mut roots = Vector::new(na); + let mut nroot = 0; + for i in 0..na { + let imaginary = f64::abs(alpha_imag[i]) > f64::EPSILON; + let infinite = f64::abs(beta[i]) < 10.0 * f64::EPSILON; + if !imaginary && !infinite { + let y_root = alpha_real[i] / beta[i]; + roots[nroot] = (xb + xa + (xb - xa) * y_root) / 2.0; + nroot += 1; + } + } + println!("nroot = {}", nroot); + for i in 0..nroot { + println!("root # {} = {}", i, roots[i]); + } + + // plot + let x_original = Vector::linspace(xa, xb, 101).unwrap(); + let y_original = x_original.get_mapped(|x| f(x)); + let y_interp = x_original.get_mapped(|x| p_interp(x)); + let y_companion = x_original.get_mapped(|x| p_companion(x, &aa, &bb)); + let mut curve1 = Curve::new(); + let mut curve2 = Curve::new(); + let mut curve3 = Curve::new(); + curve1 + .set_label("original") + .set_line_color("grey") + .set_line_width(15.0) + .draw(x_original.as_data(), y_original.as_data()); + curve2 + .set_label("interpolated") + .set_line_color("yellow") + .set_line_width(7.0) + .draw(x_original.as_data(), y_interp.as_data()); + curve3 + .set_label("companion") + .set_line_color("black") + .draw(x_original.as_data(), y_companion.as_data()); + let mut plot = Plot::new(); + let path = "/tmp/russell_lab/algo_interp_multiple_roots.svg"; + let mut legend = Legend::new(); + legend.set_outside(true).set_num_col(3); + legend.draw(); + plot.set_cross(0.0, 0.0, "grey", "-", 1.5) + .add(&curve1) + .add(&curve2) + .add(&curve3) + .add(&legend) + .grid_and_labels("$x$", "$f(x)$") + .save(path) + .unwrap(); + + Ok(()) +} diff --git a/russell_lab/src/algo/interp_lagrange.rs b/russell_lab/src/algo/interp_lagrange.rs index 943b250c..e8a4a207 100644 --- a/russell_lab/src/algo/interp_lagrange.rs +++ b/russell_lab/src/algo/interp_lagrange.rs @@ -1063,6 +1063,11 @@ impl InterpLagrange { (-1.0, 1.0) } + /// Returns and access to the barycentric coefficients + pub fn get_lambda(&self) -> &Vector { + &self.lambda + } + /// Returns the D1 matrix (calculate it if needed) pub fn get_dd1(&self) -> Result<&Matrix, StrError> { if self.dd1.dims().0 == 0 { @@ -1794,6 +1799,7 @@ mod tests { assert_eq!(interp.get_grid_type(), InterpGrid::ChebyshevGaussLobatto); assert_eq!(interp.get_points().as_data(), &[-1.0, 0.0, 1.0]); assert_eq!(interp.get_xrange(), (-1.0, 1.0)); + assert_eq!(interp.get_lambda().dim(), 3); interp.calc_dd1_matrix(); interp.calc_dd2_matrix(); assert_eq!(interp.get_dd1().unwrap().dims(), (3, 3)); From c347d0a36d6532905d7615569995bdaa789f188b Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 4 Jun 2024 16:47:12 +1000 Subject: [PATCH 02/93] [wip] Rename RootSolverBrent --- .../algo_min_and_root_solver_brent.rs | 4 +- russell_lab/src/algo/mod.rs | 6 ++- .../src/algo/multi_root_solver_interp.rs | 45 +++++++++++++++++ .../{root_solver.rs => root_solver_brent.rs} | 48 +++++++++---------- 4 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 russell_lab/src/algo/multi_root_solver_interp.rs rename russell_lab/src/algo/{root_solver.rs => root_solver_brent.rs} (87%) diff --git a/russell_lab/examples/algo_min_and_root_solver_brent.rs b/russell_lab/examples/algo_min_and_root_solver_brent.rs index 87b0f386..cc40ef0d 100644 --- a/russell_lab/examples/algo_min_and_root_solver_brent.rs +++ b/russell_lab/examples/algo_min_and_root_solver_brent.rs @@ -13,8 +13,8 @@ fn main() -> Result<(), StrError> { println!("\n{}", stats); // root - let solver = RootSolver::new(); - let (xo, stats) = solver.brent(0.3, 0.4, args, |x, _| { + let solver = RootSolverBrent::new(); + let (xo, stats) = solver.find(0.3, 0.4, args, |x, _| { Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5) })?; println!("\nx_root = {:?}", xo); diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index c8e1af9d..a840de59 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -5,16 +5,18 @@ mod interp_lagrange; mod linear_fitting; mod min_bracketing; mod min_solver; +mod multi_root_solver_interp; mod num_jacobian; mod quadrature; -mod root_solver; +mod root_solver_brent; mod testing; pub use crate::algo::common::*; pub use crate::algo::interp_lagrange::*; pub use crate::algo::linear_fitting::*; pub use crate::algo::min_bracketing::*; pub use crate::algo::min_solver::*; +pub use crate::algo::multi_root_solver_interp::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; -pub use crate::algo::root_solver::*; +pub use crate::algo::root_solver_brent::*; pub use crate::algo::testing::*; diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs new file mode 100644 index 00000000..4c829b93 --- /dev/null +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -0,0 +1,45 @@ +#![allow(unused)] + +use super::InterpLagrange; +use crate::StrError; + +pub struct MultiRootSolverInterp { + nn: usize, +} + +impl MultiRootSolverInterp { + /// Allocates a new instance + pub fn new(nn: usize) -> Self { + MultiRootSolverInterp { nn } + } + + /// Finds multiple roots using the Lagrange interpolation method + pub fn interpolation(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(), StrError> + where + F: FnMut(f64, &mut A) -> Result, + { + Err("TODO") + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::MultiRootSolverInterp; + use crate::algo::NoArgs; + + // #[test] + fn multi_root_solver_works_simple() { + // function + let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); + let (xa, xb) = (-4.0, 4.0); + + // solver + let solver = MultiRootSolverInterp::new(2); + + // find roots + let args = &mut 0; + solver.interpolation(xa, xb, args, f).unwrap(); + } +} diff --git a/russell_lab/src/algo/root_solver.rs b/russell_lab/src/algo/root_solver_brent.rs similarity index 87% rename from russell_lab/src/algo/root_solver.rs rename to russell_lab/src/algo/root_solver_brent.rs index d0c2e0a6..eac27c1c 100644 --- a/russell_lab/src/algo/root_solver.rs +++ b/russell_lab/src/algo/root_solver_brent.rs @@ -1,9 +1,9 @@ use super::Stats; use crate::StrError; -/// Implements algorithms for finding the roots of an equation +/// Implements Brent's method for finding a bracketed root of an equation #[derive(Clone, Debug)] -pub struct RootSolver { +pub struct RootSolverBrent { /// Max number of iterations /// /// ```text @@ -17,10 +17,10 @@ pub struct RootSolver { pub tolerance: f64, } -impl RootSolver { +impl RootSolverBrent { /// Allocates a new instance pub fn new() -> Self { - RootSolver { + RootSolverBrent { n_iteration_max: 100, tolerance: 1e-10, } @@ -63,20 +63,20 @@ impl RootSolver { /// # Examples /// /// ``` - /// use russell_lab::{approx_eq, RootSolver, StrError}; + /// use russell_lab::{approx_eq, RootSolverBrent, StrError}; /// /// fn main() -> Result<(), StrError> { /// let args = &mut 0; - /// let solver = RootSolver::new(); + /// let solver = RootSolverBrent::new(); /// let (xa, xb) = (-4.0, 0.0); - /// let (xo, stats) = solver.brent(xa, xb, args, |x, _| Ok(4.0 - x * x))?; + /// let (xo, stats) = solver.find(xa, xb, args, |x, _| Ok(4.0 - x * x))?; /// println!("\nroot = {:?}", xo); /// println!("\n{}", stats); /// approx_eq(xo, -2.0, 1e-10); /// Ok(()) /// } /// ``` - pub fn brent(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(f64, Stats), StrError> + pub fn find(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(f64, Stats), StrError> where F: FnMut(f64, &mut A) -> Result, { @@ -233,14 +233,14 @@ impl RootSolver { #[cfg(test)] mod tests { - use super::RootSolver; + use super::RootSolverBrent; use crate::algo::testing::get_test_functions; use crate::algo::NoArgs; use crate::approx_eq; #[test] fn validate_params_captures_errors() { - let mut solver = RootSolver::new(); + let mut solver = RootSolverBrent::new(); solver.n_iteration_max = 0; assert_eq!(solver.validate_params().err(), Some("n_iteration_max must be ≥ 2")); solver.n_iteration_max = 2; @@ -255,19 +255,19 @@ mod tests { fn brent_captures_errors_1() { let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; - let mut solver = RootSolver::new(); + let mut solver = RootSolverBrent::new(); assert_eq!(f(1.0, args).unwrap(), 0.0); assert_eq!( - solver.brent(-0.5, -0.5, args, f).err(), + solver.find(-0.5, -0.5, args, f).err(), Some("xa must be different from xb") ); assert_eq!( - solver.brent(-0.5, -0.5 - 10.0 * f64::EPSILON, args, f).err(), + solver.find(-0.5, -0.5 - 10.0 * f64::EPSILON, args, f).err(), Some("xa and xb must bracket the root and f(xa) × f(xb) < 0") ); solver.n_iteration_max = 0; assert_eq!( - solver.brent(-0.5, 2.0, args, f).err(), + solver.find(-0.5, 2.0, args, f).err(), Some("n_iteration_max must be ≥ 2") ); } @@ -288,42 +288,42 @@ mod tests { res }; let args = &mut Args { count: 0, target: 0 }; - let solver = RootSolver::new(); + let solver = RootSolverBrent::new(); // first function call - assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.find(-0.5, 2.0, args, f).err(), Some("stop")); // second function call args.count = 0; args.target = 1; - assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.find(-0.5, 2.0, args, f).err(), Some("stop")); // third function call args.count = 0; args.target = 2; - assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.find(-0.5, 2.0, args, f).err(), Some("stop")); } #[test] fn brent_works_1() { let args = &mut 0; - let solver = RootSolver::new(); + let solver = RootSolverBrent::new(); for test in &get_test_functions() { println!("\n==================================================================="); println!("\n{}", test.name); if let Some(bracket) = test.root1 { - let (xo, stats) = solver.brent(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.find(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-11); approx_eq((test.f)(xo, args).unwrap(), 0.0, test.tol_root); } if let Some(bracket) = test.root2 { - let (xo, stats) = solver.brent(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.find(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-11); approx_eq((test.f)(xo, args).unwrap(), 0.0, test.tol_root); } if let Some(bracket) = test.root3 { - let (xo, stats) = solver.brent(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.find(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-13); @@ -338,10 +338,10 @@ mod tests { let f = |x, _: &mut NoArgs| Ok(f64::powi(x - 1.0, 2) + 5.0 * f64::sin(x)); let args = &mut 0; assert!(f(1.0, args).unwrap() > 0.0); - let mut solver = RootSolver::new(); + let mut solver = RootSolverBrent::new(); solver.n_iteration_max = 2; assert_eq!( - solver.brent(-2.0, -0.7, args, f).err(), + solver.find(-2.0, -0.7, args, f).err(), Some("brent solver failed to converge") ); } From e86bc5266ba66097a6bae38f4998b1ff092dbd59 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 4 Jun 2024 18:01:24 +1000 Subject: [PATCH 03/93] [wip] Impl multi root finder --- .../examples/algo_interp_multiple_roots.rs | 12 +- .../src/algo/multi_root_solver_interp.rs | 165 ++++++++++++++++-- 2 files changed, 155 insertions(+), 22 deletions(-) diff --git a/russell_lab/examples/algo_interp_multiple_roots.rs b/russell_lab/examples/algo_interp_multiple_roots.rs index 6a2ac9c2..ebce2259 100644 --- a/russell_lab/examples/algo_interp_multiple_roots.rs +++ b/russell_lab/examples/algo_interp_multiple_roots.rs @@ -43,12 +43,12 @@ fn main() -> Result<(), StrError> { let na = 1 + np; let mut aa = Matrix::new(na, na); let mut bb = Matrix::new(na, na); - for k in 0..np { - let wk = lambda[k]; - aa.set(0, 1 + k, -uu[k]); - aa.set(1 + k, 0, wk); - aa.set(1 + k, 1 + k, yy[k]); - bb.set(1 + k, 1 + k, 1.0); + for j in 0..np { + let wj = lambda[j]; + aa.set(0, 1 + j, -uu[j]); + aa.set(1 + j, 0, wj); + aa.set(1 + j, 1 + j, yy[j]); + bb.set(1 + j, 1 + j, 1.0); } println!("A =\n{:.3}", aa); println!("B =\n{:.3}", bb); diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs index 4c829b93..cf6a99b3 100644 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -1,24 +1,141 @@ -#![allow(unused)] - -use super::InterpLagrange; use crate::StrError; +use crate::{mat_gen_eigen, Matrix, Vector}; pub struct MultiRootSolverInterp { - nn: usize, + /// Number of grid points (= N + 1) + npoint: usize, + + /// Companion matrix A + aa: Matrix, + + /// Companion matrix B + bb: Matrix, + + // For the generalized eigenvalues + alpha_real: Vector, + alpha_imag: Vector, + beta: Vector, + v: Matrix, + + /// Possible roots + roots: Vector, } impl MultiRootSolverInterp { /// Allocates a new instance - pub fn new(nn: usize) -> Self { - MultiRootSolverInterp { nn } + /// + /// # Input + /// + /// * `yy` -- holds the coordinates of the interpolant in `[-1, 1]` (e.g., Chebyshev-Gauss-Lobatto points) + /// + /// # Notes + /// + /// 1. The number of points must be ≥ 2; i.e., `yy.len() ≥ 2` + /// 2. The interpolant's degree `N` is equal to `npoint - 1` + pub fn new(yy: &Vector) -> Result { + if yy.dim() < 2 { + return Err("at least 2 grid points are required"); + } + let npoint = yy.dim(); + let nc = 1 + npoint; // companion matrix' dimension + let mut aa = Matrix::new(nc, nc); + let mut bb = Matrix::new(nc, nc); + for j in 0..npoint { + let mut prod = 1.0; + for k in 0..npoint { + if k != j { + if yy[j] == yy[k] { + return Err("grid points must not coincide"); + } + prod *= yy[j] - yy[k]; + } + } + let wj = 1.0 / prod; + aa.set(1 + j, 0, wj); + aa.set(1 + j, 1 + j, yy[j]); + bb.set(1 + j, 1 + j, 1.0); + } + Ok(MultiRootSolverInterp { + npoint, + aa: Matrix::new(nc, nc), + bb: Matrix::new(nc, nc), + alpha_real: Vector::new(nc), + alpha_imag: Vector::new(nc), + beta: Vector::new(nc), + v: Matrix::new(nc, nc), + roots: Vector::new(nc), + }) } /// Finds multiple roots using the Lagrange interpolation method - pub fn interpolation(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(), StrError> - where - F: FnMut(f64, &mut A) -> Result, - { - Err("TODO") + /// + /// The problem coordinates are `x ∈ [a, b]` and the grid coordinates are `y ∈ [-1, 1]` + /// Thus, consider the mapping: + /// + /// ```text + /// 2 x - xb - xa + /// y(x) = ————————————— + /// xb - xa + /// ``` + /// + /// And + /// + /// ```text + /// xb + xa + (xb - xa) y + /// x(y) = ————————————————————— + /// 2 + /// ``` + /// + /// The interpolated values are: + /// + /// ```text + /// Uⱼ = f(Xⱼ(Yⱼ)) + /// + /// where xa ≤ Xⱼ ≤ xb + /// and -1 ≤ Yⱼ ≤ 1 + /// ``` + /// + /// # Input + /// + /// * `uu` -- holds the function evaluations at the grid points + /// + /// # Output + /// + /// Returns the roots + pub fn find(&mut self, uu: &Vector, xa: f64, xb: f64) -> Result<&[f64], StrError> { + // check + if uu.dim() != self.npoint { + return Err("U vector must have the same dimension as the grid points vector"); + } + + // companion matrix + for j in 0..self.npoint { + self.aa.set(0, 1 + j, -uu[j]); + } + + // generalized eigenvalues + mat_gen_eigen( + &mut self.alpha_real, + &mut self.alpha_imag, + &mut self.beta, + &mut self.v, + &mut self.aa, + &mut self.bb, + )?; + + // roots = real eigenvalues + let nc = 1 + self.npoint; + let mut nroot = 0; + for i in 0..nc { + let imaginary = f64::abs(self.alpha_imag[i]) > f64::EPSILON; + let infinite = f64::abs(self.beta[i]) < 10.0 * f64::EPSILON; + if !imaginary && !infinite { + let y_root = self.alpha_real[i] / self.beta[i]; + self.roots[nroot] = (xb + xa + (xb - xa) * y_root) / 2.0; + nroot += 1; + } + } + Ok(&self.roots.as_data()[..nroot]) } } @@ -28,18 +145,34 @@ impl MultiRootSolverInterp { mod tests { use super::MultiRootSolverInterp; use crate::algo::NoArgs; + use crate::math::chebyshev_lobatto_points; + use crate::StrError; + use crate::{array_approx_eq, Vector}; - // #[test] + #[test] fn multi_root_solver_works_simple() { // function - let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); + let f = |x, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; let (xa, xb) = (-4.0, 4.0); + // grid points + let nn = 2; + let yy = chebyshev_lobatto_points(nn); + + // evaluate the data over grid points + let npoint = nn + 1; + let mut uu = Vector::new(npoint); + let args = &mut 0; + for i in 0..npoint { + let x = (xb + xa + (xb - xa) * yy[i]) / 2.0; + uu[i] = f(x, args).unwrap(); + } + // solver - let solver = MultiRootSolverInterp::new(2); + let mut solver = MultiRootSolverInterp::new(&yy).unwrap(); // find roots - let args = &mut 0; - solver.interpolation(xa, xb, args, f).unwrap(); + let roots = solver.find(&uu, xa, xb).unwrap(); + array_approx_eq(roots, &[-1.0, 1.0], 1e-15); } } From 47bb97a071dec06160eafdf9ef99538ad9699bfc Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 4 Jun 2024 20:20:11 +1000 Subject: [PATCH 04/93] Fix MultiRootSolverInterp --- .../src/algo/multi_root_solver_interp.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs index cf6a99b3..9be3944d 100644 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -11,10 +11,16 @@ pub struct MultiRootSolverInterp { /// Companion matrix B bb: Matrix, - // For the generalized eigenvalues + /// Real part of the denominator to calculate the eigenvalues alpha_real: Vector, + + /// Imaginary part of the denominator to calculate the eigenvalues alpha_imag: Vector, + + /// Denominators to calculate the eigenvalues beta: Vector, + + /// The eigenvectors (as columns) v: Matrix, /// Possible roots @@ -57,8 +63,8 @@ impl MultiRootSolverInterp { } Ok(MultiRootSolverInterp { npoint, - aa: Matrix::new(nc, nc), - bb: Matrix::new(nc, nc), + aa, + bb, alpha_real: Vector::new(nc), alpha_imag: Vector::new(nc), beta: Vector::new(nc), @@ -98,15 +104,20 @@ impl MultiRootSolverInterp { /// # Input /// /// * `uu` -- holds the function evaluations at the grid points + /// * `xa` -- the lower bound (must `be < xb`) + /// * `xb` -- the upper bound (must `be > xa`) /// /// # Output /// - /// Returns the roots + /// Returns the roots, sorted in ascending order pub fn find(&mut self, uu: &Vector, xa: f64, xb: f64) -> Result<&[f64], StrError> { // check if uu.dim() != self.npoint { return Err("U vector must have the same dimension as the grid points vector"); } + if xb <= xa { + return Err("xb must be greater than xa"); + } // companion matrix for j in 0..self.npoint { @@ -135,6 +146,10 @@ impl MultiRootSolverInterp { nroot += 1; } } + for i in nroot..nc { + self.roots[i] = f64::MAX; + } + self.roots.as_mut_data().sort_by(|a, b| a.partial_cmp(b).unwrap()); Ok(&self.roots.as_data()[..nroot]) } } From 1ed888f71e3a98b24e569ccae367c69578833b39 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Thu, 6 Jun 2024 11:24:19 +1000 Subject: [PATCH 05/93] [wip] MultiRoot solver --- .../src/algo/multi_root_solver_interp.rs | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs index 9be3944d..fa11a440 100644 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -1,16 +1,28 @@ -use crate::StrError; use crate::{mat_gen_eigen, Matrix, Vector}; +use crate::{vec_norm, Norm, StrError}; + +/// Tolerance to avoid division by zero and discard near-zero beta (the denominator of eigenvalue) values +const TOL_BETA: f64 = 100.0 * f64::EPSILON; + +/// Tolerance to discard root values outside the [xa, xb] range +const TOL_X_RANGE: f64 = 100.0 * f64::EPSILON; pub struct MultiRootSolverInterp { /// Number of grid points (= N + 1) npoint: usize, + /// Weights (aka, lambda) + w: Vector, + /// Companion matrix A aa: Matrix, /// Companion matrix B bb: Matrix, + /// Balancing coefficients + balance: Vector, + /// Real part of the denominator to calculate the eigenvalues alpha_real: Vector, @@ -44,6 +56,7 @@ impl MultiRootSolverInterp { } let npoint = yy.dim(); let nc = 1 + npoint; // companion matrix' dimension + let mut w = Vector::new(npoint); let mut aa = Matrix::new(nc, nc); let mut bb = Matrix::new(nc, nc); for j in 0..npoint { @@ -56,15 +69,16 @@ impl MultiRootSolverInterp { prod *= yy[j] - yy[k]; } } - let wj = 1.0 / prod; - aa.set(1 + j, 0, wj); + w[j] = 1.0 / prod; aa.set(1 + j, 1 + j, yy[j]); bb.set(1 + j, 1 + j, 1.0); } Ok(MultiRootSolverInterp { npoint, + w, aa, bb, + balance: Vector::new(nc), alpha_real: Vector::new(nc), alpha_imag: Vector::new(nc), beta: Vector::new(nc), @@ -119,9 +133,33 @@ impl MultiRootSolverInterp { return Err("xb must be greater than xa"); } - // companion matrix + // balancing coefficients + let nc = 1 + self.npoint; + self.balance[0] = 1.0; + for i in 0..self.npoint { + if uu[i] == 0.0 { + self.balance[1 + i] = 1.0; + } else { + self.balance[1 + i] = f64::sqrt(f64::abs(self.w[i]) / f64::abs(uu[i])); + } + } + + // scaling coefficients + let mut row0 = Vector::new(nc); + let mut col0 = Vector::new(nc); for j in 0..self.npoint { - self.aa.set(0, 1 + j, -uu[j]); + let s = self.balance[1 + j]; + let t = 1.0 / s; + row0[1 + j] = -uu[j] * s; + col0[1 + j] = t * self.w[j]; + } + let sl = vec_norm(&row0, Norm::Euc); + let sr = vec_norm(&col0, Norm::Euc); + + // (balanced) companion matrix + for k in 1..nc { + self.aa.set(0, k, sl * row0[k]); + self.aa.set(k, 0, col0[k] * sr); } // generalized eigenvalues @@ -135,15 +173,21 @@ impl MultiRootSolverInterp { )?; // roots = real eigenvalues - let nc = 1 + self.npoint; let mut nroot = 0; - for i in 0..nc { - let imaginary = f64::abs(self.alpha_imag[i]) > f64::EPSILON; - let infinite = f64::abs(self.beta[i]) < 10.0 * f64::EPSILON; + for k in 0..nc { + let imaginary = f64::abs(self.alpha_imag[k]) > 0.0; + let infinite = f64::abs(self.beta[k]) < TOL_BETA; if !imaginary && !infinite { - let y_root = self.alpha_real[i] / self.beta[i]; - self.roots[nroot] = (xb + xa + (xb - xa) * y_root) / 2.0; - nroot += 1; + let y_root = self.alpha_real[k] / self.beta[k]; + let x_root = (xb + xa + (xb - xa) * y_root) / 2.0; + println!( + "alpha = ({}, {}), beta = {:.e}, lambda = {}, x_root = {}", + self.alpha_real[k], self.alpha_imag[k], self.beta[k], y_root, x_root, + ); + if x_root >= xa - TOL_X_RANGE && x_root <= xb + TOL_X_RANGE { + self.roots[nroot] = x_root; + nroot += 1; + } } } for i in nroot..nc { @@ -171,7 +215,8 @@ mod tests { let (xa, xb) = (-4.0, 4.0); // grid points - let nn = 2; + let nn = 44; + // for nn in 2..128 { let yy = chebyshev_lobatto_points(nn); // evaluate the data over grid points @@ -188,6 +233,8 @@ mod tests { // find roots let roots = solver.find(&uu, xa, xb).unwrap(); - array_approx_eq(roots, &[-1.0, 1.0], 1e-15); + println!("N = {}, roots = {:?}", nn, roots); + array_approx_eq(roots, &[-1.0, 1.0], 1e-14); + // } } } From b328b60b206fdaf9980a2b2e1a47b9ea42f8b02f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 11 Jun 2024 22:39:03 +1000 Subject: [PATCH 06/93] [wip] Impl root finder using Chebyshev interpolation --- .vscode/settings.json | 1 + russell_lab/src/algo/mod.rs | 2 + .../src/algo/multi_root_solver_cheby.rs | 246 ++++++++++++++++++ .../src/algo/multi_root_solver_interp.rs | 6 +- 4 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 russell_lab/src/algo/multi_root_solver_cheby.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 073350c5..82bb405c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ "brusselator", "bweuler", "Cephes", + "Cheby", "Cᵢⱼₛₜ", "cmath", "condest", diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index a840de59..4c3979d8 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -5,6 +5,7 @@ mod interp_lagrange; mod linear_fitting; mod min_bracketing; mod min_solver; +mod multi_root_solver_cheby; mod multi_root_solver_interp; mod num_jacobian; mod quadrature; @@ -15,6 +16,7 @@ pub use crate::algo::interp_lagrange::*; pub use crate::algo::linear_fitting::*; pub use crate::algo::min_bracketing::*; pub use crate::algo::min_solver::*; +pub use crate::algo::multi_root_solver_cheby::*; pub use crate::algo::multi_root_solver_interp::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs new file mode 100644 index 00000000..503adedf --- /dev/null +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -0,0 +1,246 @@ +#![allow(unused)] + +use crate::math::PI; +use crate::{cpx, mat_eigen, mat_vec_mul, vec_mat_mul, StrError}; +use crate::{Matrix, Vector}; +use num_complex::{Complex64, ComplexFloat}; + +pub struct MultiRootSolverCheby { + /// Degree N + nn: usize, + + /// Chebyshev-Gauss-Lobatto coordinates + yy: Vector, + + /// Interpolation matrix P + pp: Matrix, + + /// Companion matrix A + aa: Matrix, + + /// Function evaluations at the grid points (Chebyshev-Gauss-Lobatto) + u: Vector, + + /// Coefficients of interpolation: c = P u + c: Vector, + + /// Possible roots + roots: Vector, +} + +impl MultiRootSolverCheby { + /// Allocates a new instance + pub fn new(nn: usize) -> Result { + // check + if nn < 2 { + return Err("the degree N must be ≥ 2"); + } + + // Chebyshev-Gauss-Lobatto coordinates + let yy = standard_chebyshev_lobatto_points(nn); + + // interpolation matrix + let nf = nn as f64; + let np = nn + 1; + let mut pp = Matrix::new(np, np); + for j in 0..np { + let jf = j as f64; + let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; + for k in 0..np { + let kf = k as f64; + let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; + pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); + } + } + + // companion matrix (except last row) + let mut aa = Matrix::new(nn, nn); + aa.set(0, 1, 1.0); + for r in 1..(nn - 1) { + aa.set(r, r + 1, 0.5); // upper diagonal + aa.set(r, r - 1, 0.5); // lower diagonal + } + + // done + Ok(MultiRootSolverCheby { + nn, + yy, + pp, + aa, + u: Vector::new(np), + c: Vector::new(np), + roots: Vector::new(nn), + }) + } + + // pub fn interp(&mut self, x: f64, args: &mut A, mut f: F) -> f64 + // where + // F: FnMut(f64, &mut A) -> Result, + // { + // // function evaluations at the grid points + // let nf = self.nn as f64; + // let np = self.nn + 1; + // for k in 0..np { + // let y = f64::cos(PI * (k as f64) / nf); // Chebyshev-Gauss-Lobatto + // let x = (xb + xa + (xb - xa) * y) / 2.0; + // self.u[k] = f(x, args).unwrap(); + // } + // Err("STOP") + // } + + pub fn find_given_data(&mut self, xa: f64, xb: f64, uu: &Vector) -> Result<&[f64], StrError> { + // expansion coefficients + let nn = self.nn; + let np = nn + 1; + let mut gamma = Vector::new(np); + vec_mat_mul(&mut gamma, 1.0, &uu, &self.pp).unwrap(); + let gamma_n = gamma[nn]; + if f64::abs(gamma_n) < 10.0 * f64::EPSILON { + return Err("leading expansion coefficient vanishes; try smaller degree N"); + } + for k in 0..np { + gamma[k] = -0.5 * gamma[k] / gamma_n; + } + + // nonstandard companion matrix + let mut cc = Matrix::new(nn, nn); + for i in 0..(nn - 1) { + cc.set(i, i + 1, 0.5); + cc.set(i + 1, i, 0.5); + } + cc.set(0, 1, 1.0); + for i in 0..nn { + cc.add(nn - 1, i, gamma[i]); // last row + } + + // eigenvalues + let mut l_real = Vector::new(nn); + let mut l_imag = Vector::new(nn); + let mut v_real = Matrix::new(nn, nn); + let mut v_imag = Matrix::new(nn, nn); + mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut cc).unwrap(); + + // filter the eigenvalues => roots + let cond_max = f64::min(f64::powi(2.0, (nn / 2) as i32), 1e6); + let overflow_factor = 100.0 / (nn as f64); + let overflow_root = cpx!(f64::exp(overflow_factor), 0.0); + let mut nroot = 0; + let one = cpx!(1.0, 0.0); + for i in 0..nn { + if f64::abs(l_real[i]) < 2.0 && f64::abs(l_imag[i]) < 0.2 { + let mut root = cpx!(l_real[i], l_imag[i]); + if root.abs().ln() >= overflow_factor { + root = overflow_root; + } + let z2 = root * 2.0; + let mut v_j_minus_2 = one; + let mut v_j_minus_1 = root; + let mut v_j; + let mut sum = v_j_minus_2.abs() + v_j_minus_1.abs(); // sum of the row i-th of the generalized Vandermonde matrix + for j in 2..np { + v_j = v_j_minus_1 * z2 - v_j_minus_2; + v_j_minus_2 = v_j_minus_1; + v_j_minus_1 = v_j; + sum += v_j.abs(); + } + if sum < cond_max { + if f64::abs(l_imag[i]) < 10.0 * f64::EPSILON { + self.roots[nroot] = l_real[i]; + nroot += 1; + } else { + println!("ignoring complex root"); + } + } + } + } + + // sort roots + for i in nroot..self.nn { + self.roots[i] = f64::MAX; + } + self.roots.as_mut_data().sort_by(|a, b| a.partial_cmp(b).unwrap()); + + // results + Ok(&self.roots.as_data()[..nroot]) + } +} + +/// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates +fn standard_chebyshev_lobatto_points(nn: usize) -> Vector { + let mut yy = Vector::new(nn + 1); + yy[0] = 1.0; + yy[nn] = -1.0; + if nn < 3 { + return yy; + } + let nf = nn as f64; + let d = 2.0 * nf; + let l = if (nn & 1) == 0 { + // even number of segments + nn / 2 + } else { + // odd number of segments + (nn + 3) / 2 - 1 + }; + for i in 1..l { + yy[nn - i] = -f64::sin(PI * (nf - 2.0 * (i as f64)) / d); + yy[i] = -yy[nn - i]; + } + yy +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::MultiRootSolverCheby; + use crate::algo::NoArgs; + use crate::array_approx_eq; + use crate::math::PI; + use crate::StrError; + use crate::Vector; + + #[test] + fn multi_root_solver_cheby_day_romero_paper() { + let nn = 20; + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + + let np = nn + 1; + let mut uu = Vector::new(np); + for i in 0..np { + let w = 3.0 * solver.yy[i] + 4.0; + uu[i] = f64::cos(PI * w) - 1.0 / f64::cosh(PI * w); + } + + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots = solver.find_given_data(-1.0, 1.0, &uu).unwrap(); + println!("N = {}, roots = {:?}", nn, roots); + } + + #[test] + fn multi_root_solver_cheby_works_simple() { + // function + let f = |x, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; + let (xa, xb) = (-4.0, 4.0); + + // degree + let nn = 2; + + // solver + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + + // data + let np = nn + 1; + let mut uu = Vector::new(np); + let args = &mut 0; + for i in 0..np { + let x = (xb + xa + (xb - xa) * solver.yy[i]) / 2.0; + uu[i] = f(x, args).unwrap(); + } + + // find roots + let roots = solver.find_given_data(xa, xb, &uu).unwrap(); + println!("N = {}, roots = {:?}", nn, roots); + // array_approx_eq(roots, &[-1.0, 1.0], 1e-14); + } +} diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs index fa11a440..036066c0 100644 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use crate::{mat_gen_eigen, Matrix, Vector}; use crate::{vec_norm, Norm, StrError}; @@ -209,7 +211,7 @@ mod tests { use crate::{array_approx_eq, Vector}; #[test] - fn multi_root_solver_works_simple() { + fn multi_root_solver_interp_works_simple() { // function let f = |x, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; let (xa, xb) = (-4.0, 4.0); @@ -234,7 +236,7 @@ mod tests { // find roots let roots = solver.find(&uu, xa, xb).unwrap(); println!("N = {}, roots = {:?}", nn, roots); - array_approx_eq(roots, &[-1.0, 1.0], 1e-14); + // array_approx_eq(roots, &[-1.0, 1.0], 1e-14); // } } } From fef224457e9c3abc564451c98bba5132147d940c Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 18 Jun 2024 12:02:28 +1000 Subject: [PATCH 07/93] [wip] Plot multiple roots --- .../src/algo/multi_root_solver_cheby.rs | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 503adedf..1936246b 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -96,7 +96,8 @@ impl MultiRootSolverCheby { vec_mat_mul(&mut gamma, 1.0, &uu, &self.pp).unwrap(); let gamma_n = gamma[nn]; if f64::abs(gamma_n) < 10.0 * f64::EPSILON { - return Err("leading expansion coefficient vanishes; try smaller degree N"); + println!("leading expansion coefficient vanishes; try smaller degree N"); + // return Err("leading expansion coefficient vanishes; try smaller degree N"); } for k in 0..np { gamma[k] = -0.5 * gamma[k] / gamma_n; @@ -145,7 +146,7 @@ impl MultiRootSolverCheby { } if sum < cond_max { if f64::abs(l_imag[i]) < 10.0 * f64::EPSILON { - self.roots[nroot] = l_real[i]; + self.roots[nroot] = (xb + xa + (xb - xa) * l_real[i]) / 2.0; nroot += 1; } else { println!("ignoring complex root"); @@ -199,6 +200,34 @@ mod tests { use crate::math::PI; use crate::StrError; use crate::Vector; + use plotpy::{Curve, Plot}; + + const SAVE_FIGURE: bool = true; + + fn graph(name: &str, xa: f64, xb: f64, roots: &[f64], args: &mut A, mut f: F) + where + F: FnMut(f64, &mut A) -> Result, + { + let xx = Vector::linspace(xa, xb, 101).unwrap(); + let yy = xx.get_mapped(|x| f(x, args).unwrap()); + let mut curve = Curve::new(); + let mut zeros = Curve::new(); + zeros + .set_marker_style("o") + .set_marker_color("red") + .set_marker_void(true) + .set_line_style("None"); + for root in roots { + zeros.draw(&[*root], &[0.0]); + } + curve.draw(xx.as_data(), yy.as_data()); + let mut plot = Plot::new(); + plot.add(&curve) + .add(&zeros) + .set_cross(0.0, 0.0, "gray", "-", 1.0) + .grid_and_labels("x", "f(x)") + .save(&format!("/tmp/russell/{}.svg", name)); + } #[test] fn multi_root_solver_cheby_day_romero_paper() { @@ -215,16 +244,23 @@ mod tests { let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let roots = solver.find_given_data(-1.0, 1.0, &uu).unwrap(); println!("N = {}, roots = {:?}", nn, roots); + + if SAVE_FIGURE { + let f = |x, _: &mut NoArgs| -> Result { Ok(f64::cos(PI * x) - 1.0 / f64::cosh(PI * x)) }; + let (xa, xb) = (-1.0, 1.0); + let args = &mut 0; + graph("test_multi_root_solver_cheby_day_romero_paper", xa, xb, roots, args, f); + } } #[test] - fn multi_root_solver_cheby_works_simple() { + fn multi_root_solver_cheby_simple() { // function let f = |x, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; let (xa, xb) = (-4.0, 4.0); // degree - let nn = 2; + let nn = 4; // solver let mut solver = MultiRootSolverCheby::new(nn).unwrap(); @@ -242,5 +278,9 @@ mod tests { let roots = solver.find_given_data(xa, xb, &uu).unwrap(); println!("N = {}, roots = {:?}", nn, roots); // array_approx_eq(roots, &[-1.0, 1.0], 1e-14); + + if SAVE_FIGURE { + graph("test_multi_root_solver_cheby_simple", xa, xb, roots, args, f); + } } } From cb791c6921a87bf6aae526a286ab7bc57c7ba20d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 18 Jun 2024 12:42:04 +1000 Subject: [PATCH 08/93] [wip] Multiple roots tests --- .../src/algo/multi_root_solver_cheby.rs | 7 ++-- .../src/algo/multi_root_solver_interp.rs | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 1936246b..4f725082 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -246,7 +246,10 @@ mod tests { println!("N = {}, roots = {:?}", nn, roots); if SAVE_FIGURE { - let f = |x, _: &mut NoArgs| -> Result { Ok(f64::cos(PI * x) - 1.0 / f64::cosh(PI * x)) }; + let f = |x, _: &mut NoArgs| -> Result { + let w = 3.0 * x + 4.0; + Ok(f64::cos(PI * w) - 1.0 / f64::cosh(PI * w)) + }; let (xa, xb) = (-1.0, 1.0); let args = &mut 0; graph("test_multi_root_solver_cheby_day_romero_paper", xa, xb, roots, args, f); @@ -260,7 +263,7 @@ mod tests { let (xa, xb) = (-4.0, 4.0); // degree - let nn = 4; + let nn = 8; // 7 causes Inf and NaN which causes segmentation fault in LAPACK // solver let mut solver = MultiRootSolverCheby::new(nn).unwrap(); diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs index 036066c0..bf0fa11e 100644 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -209,6 +209,34 @@ mod tests { use crate::math::chebyshev_lobatto_points; use crate::StrError; use crate::{array_approx_eq, Vector}; + use plotpy::{Curve, Plot}; + + const SAVE_FIGURE: bool = true; + + fn graph(name: &str, xa: f64, xb: f64, roots: &[f64], args: &mut A, mut f: F) + where + F: FnMut(f64, &mut A) -> Result, + { + let xx = Vector::linspace(xa, xb, 101).unwrap(); + let yy = xx.get_mapped(|x| f(x, args).unwrap()); + let mut curve = Curve::new(); + let mut zeros = Curve::new(); + zeros + .set_marker_style("o") + .set_marker_color("red") + .set_marker_void(true) + .set_line_style("None"); + for root in roots { + zeros.draw(&[*root], &[0.0]); + } + curve.draw(xx.as_data(), yy.as_data()); + let mut plot = Plot::new(); + plot.add(&curve) + .add(&zeros) + .set_cross(0.0, 0.0, "gray", "-", 1.0) + .grid_and_labels("x", "f(x)") + .save(&format!("/tmp/russell/{}.svg", name)); + } #[test] fn multi_root_solver_interp_works_simple() { @@ -238,5 +266,9 @@ mod tests { println!("N = {}, roots = {:?}", nn, roots); // array_approx_eq(roots, &[-1.0, 1.0], 1e-14); // } + + if SAVE_FIGURE { + graph("test_multi_root_solver_interp_simple", xa, xb, roots, args, f); + } } } From 891b8576a4fb634183c77c07b98c6b37463c6f4a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 18 Jun 2024 12:47:44 +1000 Subject: [PATCH 09/93] [wip] Multiple roots tests --- russell_lab/src/algo/multi_root_solver_cheby.rs | 2 +- russell_lab/src/algo/multi_root_solver_interp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 4f725082..777bb3d6 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -218,7 +218,7 @@ mod tests { .set_marker_void(true) .set_line_style("None"); for root in roots { - zeros.draw(&[*root], &[0.0]); + zeros.draw(&[*root], &[f(*root, args).unwrap()]); } curve.draw(xx.as_data(), yy.as_data()); let mut plot = Plot::new(); diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs index bf0fa11e..0f1103ba 100644 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ b/russell_lab/src/algo/multi_root_solver_interp.rs @@ -227,7 +227,7 @@ mod tests { .set_marker_void(true) .set_line_style("None"); for root in roots { - zeros.draw(&[*root], &[0.0]); + zeros.draw(&[*root], &[f(*root, args).unwrap()]); } curve.draw(xx.as_data(), yy.as_data()); let mut plot = Plot::new(); From 521c57a9b79beeca1316b3e34f2c5a828ca5b3f6 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 19 Jun 2024 10:20:36 +1000 Subject: [PATCH 10/93] [wip] Use Boyd's tolerances --- .../src/algo/multi_root_solver_cheby.rs | 114 +++++++++--------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 777bb3d6..03ac433d 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -5,6 +5,9 @@ use crate::{cpx, mat_eigen, mat_vec_mul, vec_mat_mul, StrError}; use crate::{Matrix, Vector}; use num_complex::{Complex64, ComplexFloat}; +const TOL_TAU: f64 = 1.0e-8; // discard roots with abs(Im(root)) > tau +const TOL_SIGMA: f64 = 1.0e-6; // discard roots such that abs(Re(root)) > (1 + sigma) + pub struct MultiRootSolverCheby { /// Degree N nn: usize, @@ -39,7 +42,9 @@ impl MultiRootSolverCheby { // Chebyshev-Gauss-Lobatto coordinates let yy = standard_chebyshev_lobatto_points(nn); - // interpolation matrix + // println!("yy =\n{}", yy); + + // interpolation matrix (Boyd's phia) let nf = nn as f64; let np = nn + 1; let mut pp = Matrix::new(np, np); @@ -52,6 +57,7 @@ impl MultiRootSolverCheby { pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); } } + println!("P =\n{:.4}", pp); // companion matrix (except last row) let mut aa = Matrix::new(nn, nn); @@ -60,6 +66,7 @@ impl MultiRootSolverCheby { aa.set(r, r + 1, 0.5); // upper diagonal aa.set(r, r - 1, 0.5); // lower diagonal } + println!("A =\n{:.4}", aa); // done Ok(MultiRootSolverCheby { @@ -94,6 +101,7 @@ impl MultiRootSolverCheby { let np = nn + 1; let mut gamma = Vector::new(np); vec_mat_mul(&mut gamma, 1.0, &uu, &self.pp).unwrap(); + println!("gamma =\n{}", gamma); let gamma_n = gamma[nn]; if f64::abs(gamma_n) < 10.0 * f64::EPSILON { println!("leading expansion coefficient vanishes; try smaller degree N"); @@ -102,6 +110,7 @@ impl MultiRootSolverCheby { for k in 0..np { gamma[k] = -0.5 * gamma[k] / gamma_n; } + println!("gamma (normalized) =\n{}", gamma); // nonstandard companion matrix let mut cc = Matrix::new(nn, nn); @@ -114,43 +123,30 @@ impl MultiRootSolverCheby { cc.add(nn - 1, i, gamma[i]); // last row } + // last row of the companion matrix + for i in 0..nn { + self.aa.set(nn - 1, i, gamma[i]); // last row + } + self.aa.add(nn - 1, nn - 2, 0.5); + println!("A =\n{:.4}", self.aa); + // eigenvalues let mut l_real = Vector::new(nn); let mut l_imag = Vector::new(nn); let mut v_real = Matrix::new(nn, nn); let mut v_imag = Matrix::new(nn, nn); - mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut cc).unwrap(); + mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut self.aa).unwrap(); + + println!("l_real =\n{}", l_real); + println!("l_imag =\n{}", l_imag); - // filter the eigenvalues => roots - let cond_max = f64::min(f64::powi(2.0, (nn / 2) as i32), 1e6); - let overflow_factor = 100.0 / (nn as f64); - let overflow_root = cpx!(f64::exp(overflow_factor), 0.0); + // roots = real eigenvalues within the interval let mut nroot = 0; - let one = cpx!(1.0, 0.0); for i in 0..nn { - if f64::abs(l_real[i]) < 2.0 && f64::abs(l_imag[i]) < 0.2 { - let mut root = cpx!(l_real[i], l_imag[i]); - if root.abs().ln() >= overflow_factor { - root = overflow_root; - } - let z2 = root * 2.0; - let mut v_j_minus_2 = one; - let mut v_j_minus_1 = root; - let mut v_j; - let mut sum = v_j_minus_2.abs() + v_j_minus_1.abs(); // sum of the row i-th of the generalized Vandermonde matrix - for j in 2..np { - v_j = v_j_minus_1 * z2 - v_j_minus_2; - v_j_minus_2 = v_j_minus_1; - v_j_minus_1 = v_j; - sum += v_j.abs(); - } - if sum < cond_max { - if f64::abs(l_imag[i]) < 10.0 * f64::EPSILON { - self.roots[nroot] = (xb + xa + (xb - xa) * l_real[i]) / 2.0; - nroot += 1; - } else { - println!("ignoring complex root"); - } + if f64::abs(l_imag[i]) < TOL_TAU * f64::abs(l_real[i]) { + if f64::abs(l_real[i]) <= (1.0 + TOL_SIGMA) { + self.roots[nroot] = (xb + xa + (xb - xa) * l_real[i]) / 2.0; + nroot += 1; } } } @@ -229,33 +225,6 @@ mod tests { .save(&format!("/tmp/russell/{}.svg", name)); } - #[test] - fn multi_root_solver_cheby_day_romero_paper() { - let nn = 20; - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - - let np = nn + 1; - let mut uu = Vector::new(np); - for i in 0..np { - let w = 3.0 * solver.yy[i] + 4.0; - uu[i] = f64::cos(PI * w) - 1.0 / f64::cosh(PI * w); - } - - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots = solver.find_given_data(-1.0, 1.0, &uu).unwrap(); - println!("N = {}, roots = {:?}", nn, roots); - - if SAVE_FIGURE { - let f = |x, _: &mut NoArgs| -> Result { - let w = 3.0 * x + 4.0; - Ok(f64::cos(PI * w) - 1.0 / f64::cosh(PI * w)) - }; - let (xa, xb) = (-1.0, 1.0); - let args = &mut 0; - graph("test_multi_root_solver_cheby_day_romero_paper", xa, xb, roots, args, f); - } - } - #[test] fn multi_root_solver_cheby_simple() { // function @@ -263,7 +232,7 @@ mod tests { let (xa, xb) = (-4.0, 4.0); // degree - let nn = 8; // 7 causes Inf and NaN which causes segmentation fault in LAPACK + let nn = 2; // 7 causes Inf and NaN which causes segmentation fault in LAPACK // solver let mut solver = MultiRootSolverCheby::new(nn).unwrap(); @@ -274,8 +243,10 @@ mod tests { let args = &mut 0; for i in 0..np { let x = (xb + xa + (xb - xa) * solver.yy[i]) / 2.0; + // println!("x = {}", x); uu[i] = f(x, args).unwrap(); } + println!("U =\n{}", uu); // find roots let roots = solver.find_given_data(xa, xb, &uu).unwrap(); @@ -286,4 +257,31 @@ mod tests { graph("test_multi_root_solver_cheby_simple", xa, xb, roots, args, f); } } + + #[test] + fn multi_root_solver_cheby_day_romero_paper() { + let nn = 20; + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + + let np = nn + 1; + let mut uu = Vector::new(np); + for i in 0..np { + let w = 3.0 * solver.yy[i] + 4.0; + uu[i] = f64::cos(PI * w) - 1.0 / f64::cosh(PI * w); + } + + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots = solver.find_given_data(-1.0, 1.0, &uu).unwrap(); + println!("N = {}, roots = {:?}", nn, roots); + + if SAVE_FIGURE { + let f = |x, _: &mut NoArgs| -> Result { + let w = 3.0 * x + 4.0; + Ok(f64::cos(PI * w) - 1.0 / f64::cosh(PI * w)) + }; + let (xa, xb) = (-1.0, 1.0); + let args = &mut 0; + graph("test_multi_root_solver_cheby_day_romero_paper", xa, xb, roots, args, f); + } + } } From f577bd56a587393f658633bc86883d2e81ab9af6 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 19 Jun 2024 12:32:45 +1000 Subject: [PATCH 11/93] [wip] Clean multi root cheby a little --- .../src/algo/multi_root_solver_cheby.rs | 207 +++++++++--------- 1 file changed, 109 insertions(+), 98 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 03ac433d..744da697 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,17 +1,23 @@ -#![allow(unused)] - -use crate::math::PI; -use crate::{cpx, mat_eigen, mat_vec_mul, vec_mat_mul, StrError}; +use crate::math::{chebyshev_tn, PI}; +use crate::{mat_eigen, mat_vec_mul, StrError}; use crate::{Matrix, Vector}; -use num_complex::{Complex64, ComplexFloat}; -const TOL_TAU: f64 = 1.0e-8; // discard roots with abs(Im(root)) > tau -const TOL_SIGMA: f64 = 1.0e-6; // discard roots such that abs(Re(root)) > (1 + sigma) +/// Tolerance to avoid division by zero on the trailing Chebyshev coefficient +const TOL_EPS: f64 = 1.0e-13; + +/// Tolerance to discard roots with abs(Im(root)) > tau +const TOL_TAU: f64 = 1.0e-8; + +/// Tolerance to discard roots such that abs(Re(root)) > (1 + sigma) +const TOL_SIGMA: f64 = 1.0e-6; pub struct MultiRootSolverCheby { /// Degree N nn: usize, + /// Number of grid points (= N + 1) + np: usize, + /// Chebyshev-Gauss-Lobatto coordinates yy: Vector, @@ -21,7 +27,7 @@ pub struct MultiRootSolverCheby { /// Companion matrix A aa: Matrix, - /// Function evaluations at the grid points (Chebyshev-Gauss-Lobatto) + /// Function evaluations at the (standard) Chebyshev-Gauss-Lobatto grid points u: Vector, /// Coefficients of interpolation: c = P u @@ -29,6 +35,15 @@ pub struct MultiRootSolverCheby { /// Possible roots roots: Vector, + + /// Indicates whether the data vector (u) has been set or not + all_data_set: bool, + + /// Lower bound + xa: f64, + + /// Upper bound + xb: f64, } impl MultiRootSolverCheby { @@ -39,12 +54,10 @@ impl MultiRootSolverCheby { return Err("the degree N must be ≥ 2"); } - // Chebyshev-Gauss-Lobatto coordinates + // standard Chebyshev-Gauss-Lobatto coordinates let yy = standard_chebyshev_lobatto_points(nn); - // println!("yy =\n{}", yy); - - // interpolation matrix (Boyd's phia) + // interpolation matrix let nf = nn as f64; let np = nn + 1; let mut pp = Matrix::new(np, np); @@ -57,7 +70,7 @@ impl MultiRootSolverCheby { pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); } } - println!("P =\n{:.4}", pp); + println!("P = \n{:.5}", pp); // companion matrix (except last row) let mut aa = Matrix::new(nn, nn); @@ -66,66 +79,87 @@ impl MultiRootSolverCheby { aa.set(r, r + 1, 0.5); // upper diagonal aa.set(r, r - 1, 0.5); // lower diagonal } - println!("A =\n{:.4}", aa); // done Ok(MultiRootSolverCheby { nn, + np, yy, pp, aa, u: Vector::new(np), c: Vector::new(np), roots: Vector::new(nn), + all_data_set: false, + xa: -1.0, + xb: 1.0, }) } - // pub fn interp(&mut self, x: f64, args: &mut A, mut f: F) -> f64 - // where - // F: FnMut(f64, &mut A) -> Result, - // { - // // function evaluations at the grid points - // let nf = self.nn as f64; - // let np = self.nn + 1; - // for k in 0..np { - // let y = f64::cos(PI * (k as f64) / nf); // Chebyshev-Gauss-Lobatto - // let x = (xb + xa + (xb - xa) * y) / 2.0; - // self.u[k] = f(x, args).unwrap(); - // } - // Err("STOP") - // } - - pub fn find_given_data(&mut self, xa: f64, xb: f64, uu: &Vector) -> Result<&[f64], StrError> { - // expansion coefficients - let nn = self.nn; - let np = nn + 1; - let mut gamma = Vector::new(np); - vec_mat_mul(&mut gamma, 1.0, &uu, &self.pp).unwrap(); - println!("gamma =\n{}", gamma); - let gamma_n = gamma[nn]; - if f64::abs(gamma_n) < 10.0 * f64::EPSILON { - println!("leading expansion coefficient vanishes; try smaller degree N"); - // return Err("leading expansion coefficient vanishes; try smaller degree N"); + /// Sets the data vector (u) from the function evaluated at the standard Chebyshev-Gauss-Lobatto points + pub fn set_data_from_function(&mut self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(), StrError> + where + F: FnMut(f64, &mut A) -> Result, + { + // check + if xb <= xa + TOL_EPS { + return Err("xb must be greater than xa + ϵ"); } - for k in 0..np { - gamma[k] = -0.5 * gamma[k] / gamma_n; + + // set data vector + for k in 0..self.np { + let x = (xb + xa + (xb - xa) * self.yy[k]) / 2.0; + self.u[k] = f(x, args).unwrap(); } - println!("gamma (normalized) =\n{}", gamma); - // nonstandard companion matrix - let mut cc = Matrix::new(nn, nn); - for i in 0..(nn - 1) { - cc.set(i, i + 1, 0.5); - cc.set(i + 1, i, 0.5); + // calculate the Chebyshev coefficients + mat_vec_mul(&mut self.c, 1.0, &self.pp, &self.u).unwrap(); + println!("c = \n{:.5}", self.c); + + // done + self.all_data_set = true; + self.xa = xa; + self.xb = xb; + Ok(()) + } + + /// Computes the interpolated function + /// + /// **Warning:** The data vector (u) must be set first. + pub fn interp(&self, x: f64) -> Result { + if !self.all_data_set { + return Err("The data vector (u) must be set first"); } - cc.set(0, 1, 1.0); - for i in 0..nn { - cc.add(nn - 1, i, gamma[i]); // last row + if x < self.xa || x > self.xb { + return Err("x must be in [xa, xb]"); + } + let y = (2.0 * x - self.xb - self.xa) / (self.xb - self.xa); + let mut sum = 0.0; + for k in 0..self.np { + sum += self.c[k] * chebyshev_tn(k, y); + } + Ok(sum) + } + + /// Find all roots in the interval + /// + /// **Warning:** The data vector (u) must be set first. + pub fn find(&mut self) -> Result<&[f64], StrError> { + // check + if !self.all_data_set { + return Err("The data vector (u) must be set first"); + } + + // expansion coefficients + let nn = self.nn; + let cn = self.c[nn]; + if f64::abs(cn) < TOL_EPS { + return Err("trailing Chebyshev coefficient vanishes; try smaller degree N"); } // last row of the companion matrix - for i in 0..nn { - self.aa.set(nn - 1, i, gamma[i]); // last row + for k in 0..nn { + self.aa.set(nn - 1, k, -0.5 * self.c[k] / cn); } self.aa.add(nn - 1, nn - 2, 0.5); println!("A =\n{:.4}", self.aa); @@ -145,14 +179,14 @@ impl MultiRootSolverCheby { for i in 0..nn { if f64::abs(l_imag[i]) < TOL_TAU * f64::abs(l_real[i]) { if f64::abs(l_real[i]) <= (1.0 + TOL_SIGMA) { - self.roots[nroot] = (xb + xa + (xb - xa) * l_real[i]) / 2.0; + self.roots[nroot] = (self.xb + self.xa + (self.xb - self.xa) * l_real[i]) / 2.0; nroot += 1; } } } // sort roots - for i in nroot..self.nn { + for i in nroot..nn { self.roots[i] = f64::MAX; } self.roots.as_mut_data().sort_by(|a, b| a.partial_cmp(b).unwrap()); @@ -193,14 +227,13 @@ mod tests { use super::MultiRootSolverCheby; use crate::algo::NoArgs; use crate::array_approx_eq; - use crate::math::PI; use crate::StrError; use crate::Vector; use plotpy::{Curve, Plot}; const SAVE_FIGURE: bool = true; - fn graph(name: &str, xa: f64, xb: f64, roots: &[f64], args: &mut A, mut f: F) + fn graph(xa: f64, xb: f64, roots: &[f64], args: &mut A, mut f: F) -> Plot where F: FnMut(f64, &mut A) -> Result, { @@ -210,8 +243,8 @@ mod tests { let mut zeros = Curve::new(); zeros .set_marker_style("o") - .set_marker_color("red") .set_marker_void(true) + .set_marker_line_color("#00760F") .set_line_style("None"); for root in roots { zeros.draw(&[*root], &[f(*root, args).unwrap()]); @@ -221,8 +254,8 @@ mod tests { plot.add(&curve) .add(&zeros) .set_cross(0.0, 0.0, "gray", "-", 1.0) - .grid_and_labels("x", "f(x)") - .save(&format!("/tmp/russell/{}.svg", name)); + .grid_and_labels("x", "f(x)"); + plot } #[test] @@ -232,56 +265,34 @@ mod tests { let (xa, xb) = (-4.0, 4.0); // degree - let nn = 2; // 7 causes Inf and NaN which causes segmentation fault in LAPACK + let nn = 2; // solver let mut solver = MultiRootSolverCheby::new(nn).unwrap(); // data - let np = nn + 1; - let mut uu = Vector::new(np); let args = &mut 0; - for i in 0..np { - let x = (xb + xa + (xb - xa) * solver.yy[i]) / 2.0; - // println!("x = {}", x); - uu[i] = f(x, args).unwrap(); - } - println!("U =\n{}", uu); + solver.set_data_from_function(xa, xb, args, f).unwrap(); + println!("U =\n{}", solver.u); // find roots - let roots = solver.find_given_data(xa, xb, &uu).unwrap(); + let roots = Vec::from(solver.find().unwrap()); println!("N = {}, roots = {:?}", nn, roots); - // array_approx_eq(roots, &[-1.0, 1.0], 1e-14); + // figure if SAVE_FIGURE { - graph("test_multi_root_solver_cheby_simple", xa, xb, roots, args, f); + let mut curve = Curve::new(); + curve.set_line_style("--").set_marker_style(".").set_marker_every(5); + let xx = Vector::linspace(xa, xb, 101).unwrap(); + let yy = xx.get_mapped(|x| solver.interp(x).unwrap()); + curve.draw(xx.as_data(), yy.as_data()); + let mut plot = graph(xa, xb, &roots, args, f); + plot.add(&curve) + .save("/tmp/russell/test_multi_root_solver_cheby_simple.svg") + .unwrap(); } - } - #[test] - fn multi_root_solver_cheby_day_romero_paper() { - let nn = 20; - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - - let np = nn + 1; - let mut uu = Vector::new(np); - for i in 0..np { - let w = 3.0 * solver.yy[i] + 4.0; - uu[i] = f64::cos(PI * w) - 1.0 / f64::cosh(PI * w); - } - - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots = solver.find_given_data(-1.0, 1.0, &uu).unwrap(); - println!("N = {}, roots = {:?}", nn, roots); - - if SAVE_FIGURE { - let f = |x, _: &mut NoArgs| -> Result { - let w = 3.0 * x + 4.0; - Ok(f64::cos(PI * w) - 1.0 / f64::cosh(PI * w)) - }; - let (xa, xb) = (-1.0, 1.0); - let args = &mut 0; - graph("test_multi_root_solver_cheby_day_romero_paper", xa, xb, roots, args, f); - } + // check + array_approx_eq(&roots, &[-1.0, 1.0], 1e-14); } } From 36e65413e7ba1a76d1f86bc3e88ee68028a578f7 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 19 Jun 2024 13:05:14 +1000 Subject: [PATCH 12/93] [wip] Test multi root solver --- .../src/algo/multi_root_solver_cheby.rs | 79 +++++++++++++++---- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 744da697..8623a36d 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -154,7 +154,7 @@ impl MultiRootSolverCheby { let nn = self.nn; let cn = self.c[nn]; if f64::abs(cn) < TOL_EPS { - return Err("trailing Chebyshev coefficient vanishes; try smaller degree N"); + return Err("trailing Chebyshev coefficient vanishes; try another degree N"); } // last row of the companion matrix @@ -227,20 +227,35 @@ mod tests { use super::MultiRootSolverCheby; use crate::algo::NoArgs; use crate::array_approx_eq; + use crate::get_test_functions; use crate::StrError; use crate::Vector; + use plotpy::Legend; use plotpy::{Curve, Plot}; const SAVE_FIGURE: bool = true; - fn graph(xa: f64, xb: f64, roots: &[f64], args: &mut A, mut f: F) -> Plot + fn graph( + name: &str, + xa: f64, + xb: f64, + solver: &MultiRootSolverCheby, + roots: &[f64], + args: &mut A, + mut f: F, + ) -> Plot where F: FnMut(f64, &mut A) -> Result, { let xx = Vector::linspace(xa, xb, 101).unwrap(); - let yy = xx.get_mapped(|x| f(x, args).unwrap()); - let mut curve = Curve::new(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| solver.interp(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); let mut zeros = Curve::new(); + curve_ana.set_label("analytical"); + curve_int.set_label("interpolated"); + curve_int.set_line_style("--").set_marker_style(".").set_marker_every(5); zeros .set_marker_style("o") .set_marker_void(true) @@ -249,12 +264,21 @@ mod tests { for root in roots { zeros.draw(&[*root], &[f(*root, args).unwrap()]); } - curve.draw(xx.as_data(), yy.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); let mut plot = Plot::new(); - plot.add(&curve) + let mut legend = Legend::new(); + legend.set_num_col(2); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) .add(&zeros) - .set_cross(0.0, 0.0, "gray", "-", 1.0) - .grid_and_labels("x", "f(x)"); + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save(&format!("/tmp/russell/{}.svg", name)) + .unwrap(); plot } @@ -281,18 +305,39 @@ mod tests { // figure if SAVE_FIGURE { - let mut curve = Curve::new(); - curve.set_line_style("--").set_marker_style(".").set_marker_every(5); - let xx = Vector::linspace(xa, xb, 101).unwrap(); - let yy = xx.get_mapped(|x| solver.interp(x).unwrap()); - curve.draw(xx.as_data(), yy.as_data()); - let mut plot = graph(xa, xb, &roots, args, f); - plot.add(&curve) - .save("/tmp/russell/test_multi_root_solver_cheby_simple.svg") - .unwrap(); + graph("test_multi_root_solver_cheby_simple", xa, xb, &solver, &roots, args, f); } // check array_approx_eq(&roots, &[-1.0, 1.0], 1e-14); } + + #[test] + fn multi_root_solver_cheby_works() { + let tests = get_test_functions(); + let id = 2; + let test = &tests[id]; + if test.root1.is_some() || test.root2.is_some() || test.root3.is_some() { + println!("\n==================================================================="); + println!("\n{}", test.name); + let (xa, xb) = test.range; + let nn = 10; + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let args = &mut 0; + solver.set_data_from_function(xa, xb, args, test.f).unwrap(); + let roots = Vec::from(solver.find().unwrap()); + println!("roots = {:?}", roots); + if SAVE_FIGURE { + graph( + &format!("test_multi_root_solver_cheby_{:0>3}", id), + xa, + xb, + &solver, + &roots, + args, + test.f, + ); + } + } + } } From 29221a685c6d4404d004d4f5aa00a9e95e56c397 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 19 Jun 2024 13:44:13 +1000 Subject: [PATCH 13/93] [wip] Impl polish roots --- .../src/algo/multi_root_solver_cheby.rs | 102 +++++++++++++++--- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 8623a36d..54755442 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,5 +1,5 @@ use crate::math::{chebyshev_tn, PI}; -use crate::{mat_eigen, mat_vec_mul, StrError}; +use crate::{mat_eigen, mat_vec_mul, RootSolverBrent, StrError}; use crate::{Matrix, Vector}; /// Tolerance to avoid division by zero on the trailing Chebyshev coefficient @@ -196,6 +196,51 @@ impl MultiRootSolverCheby { } } +/// Polishes the root using Brent's method +pub fn polish_roots( + roots_out: &mut [f64], + roots_in: &[f64], + xa: f64, + xb: f64, + args: &mut A, + mut f: F, +) -> Result<(), StrError> +where + F: FnMut(f64, &mut A) -> Result, +{ + let nr = roots_in.len(); + if nr < 2 { + return Err("this function works with at least two roots"); + } + let solver = RootSolverBrent::new(); + let l = nr - 1; + for i in 0..nr { + let xr = roots_in[i]; + if xr < xa || xr > xb { + return Err("a root is outside [xa, xb]"); + } + let a = if i == 0 { + xa + } else { + (roots_in[i - 1] + roots_in[i]) / 2.0 + }; + let b = if i == l { + xb + } else { + (roots_in[i] + roots_in[i + 1]) / 2.0 + }; + let fa = f(a, args)?; + let fb = f(b, args)?; + if fa * fb < 0.0 { + let (xo, _) = solver.find(a, b, args, &mut f)?; + roots_out[i] = xo; + } else { + roots_out[i] = roots_in[i]; + } + } + Ok(()) +} + /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates fn standard_chebyshev_lobatto_points(nn: usize) -> Vector { let mut yy = Vector::new(nn + 1); @@ -228,6 +273,7 @@ mod tests { use crate::algo::NoArgs; use crate::array_approx_eq; use crate::get_test_functions; + use crate::polish_roots; use crate::StrError; use crate::Vector; use plotpy::Legend; @@ -240,7 +286,8 @@ mod tests { xa: f64, xb: f64, solver: &MultiRootSolverCheby, - roots: &[f64], + roots_unpolished: &[f64], + roots_polished: &[f64], args: &mut A, mut f: F, ) -> Plot @@ -252,17 +299,27 @@ mod tests { let yy_int = xx.get_mapped(|x| solver.interp(x).unwrap()); let mut curve_ana = Curve::new(); let mut curve_int = Curve::new(); - let mut zeros = Curve::new(); + let mut zeros_unpolished = Curve::new(); + let mut zeros_polished = Curve::new(); curve_ana.set_label("analytical"); curve_int.set_label("interpolated"); curve_int.set_line_style("--").set_marker_style(".").set_marker_every(5); - zeros + zeros_unpolished .set_marker_style("o") .set_marker_void(true) .set_marker_line_color("#00760F") .set_line_style("None"); - for root in roots { - zeros.draw(&[*root], &[f(*root, args).unwrap()]); + zeros_polished + .set_marker_style("s") + .set_marker_size(10.0) + .set_marker_void(true) + .set_marker_line_color("#00760F") + .set_line_style("None"); + for root in roots_unpolished { + zeros_unpolished.draw(&[*root], &[solver.interp(*root).unwrap()]); + } + for root in roots_polished { + zeros_polished.draw(&[*root], &[f(*root, args).unwrap()]); } curve_int.draw(xx.as_data(), yy_int.as_data()); curve_ana.draw(xx.as_data(), yy_ana.as_data()); @@ -273,7 +330,8 @@ mod tests { legend.draw(); plot.add(&curve_ana) .add(&curve_int) - .add(&zeros) + .add(&zeros_unpolished) + .add(&zeros_polished) .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") @@ -300,40 +358,52 @@ mod tests { println!("U =\n{}", solver.u); // find roots - let roots = Vec::from(solver.find().unwrap()); - println!("N = {}, roots = {:?}", nn, roots); + let roots_unpolished = Vec::from(solver.find().unwrap()); + let mut roots_polished = vec![0.0; roots_unpolished.len()]; + polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, f).unwrap(); // figure if SAVE_FIGURE { - graph("test_multi_root_solver_cheby_simple", xa, xb, &solver, &roots, args, f); + graph( + "test_multi_root_solver_cheby_simple", + xa, + xb, + &solver, + &roots_unpolished, + &roots_polished, + args, + f, + ); } // check - array_approx_eq(&roots, &[-1.0, 1.0], 1e-14); + array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-12); } #[test] fn multi_root_solver_cheby_works() { let tests = get_test_functions(); - let id = 2; + let id = 3; let test = &tests[id]; if test.root1.is_some() || test.root2.is_some() || test.root3.is_some() { println!("\n==================================================================="); println!("\n{}", test.name); let (xa, xb) = test.range; - let nn = 10; + let nn = 4; let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let args = &mut 0; solver.set_data_from_function(xa, xb, args, test.f).unwrap(); - let roots = Vec::from(solver.find().unwrap()); - println!("roots = {:?}", roots); + let roots_unpolished = Vec::from(solver.find().unwrap()); + let mut roots_polished = vec![0.0; roots_unpolished.len()]; + polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f).unwrap(); if SAVE_FIGURE { graph( &format!("test_multi_root_solver_cheby_{:0>3}", id), xa, xb, &solver, - &roots, + &roots_unpolished, + &roots_polished, args, test.f, ); From 34210570c3b5d045f3c9c2b0b341bf830b539b5a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Thu, 20 Jun 2024 09:56:12 +1000 Subject: [PATCH 14/93] [wip] Chebyshev interpolation --- russell_lab/src/algo/multi_root_solver_cheby.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 54755442..b670a88c 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -383,19 +383,20 @@ mod tests { #[test] fn multi_root_solver_cheby_works() { let tests = get_test_functions(); - let id = 3; + let id = 4; let test = &tests[id]; if test.root1.is_some() || test.root2.is_some() || test.root3.is_some() { println!("\n==================================================================="); println!("\n{}", test.name); let (xa, xb) = test.range; - let nn = 4; + let nn = 20; let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let args = &mut 0; solver.set_data_from_function(xa, xb, args, test.f).unwrap(); let roots_unpolished = Vec::from(solver.find().unwrap()); - let mut roots_polished = vec![0.0; roots_unpolished.len()]; - polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f).unwrap(); + // let mut roots_polished = vec![0.0; roots_unpolished.len()]; + // polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f).unwrap(); + let roots_polished = roots_unpolished.clone(); if SAVE_FIGURE { graph( &format!("test_multi_root_solver_cheby_{:0>3}", id), From 5916a46f1e10c213b70e71669b9d6859b9232f59 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Thu, 20 Jun 2024 16:11:11 +1000 Subject: [PATCH 15/93] [wip] Impl adaptive interpolation --- .../src/algo/multi_root_solver_cheby.rs | 180 ++++++++++++++++-- 1 file changed, 163 insertions(+), 17 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index b670a88c..725b6630 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,5 +1,9 @@ +#![allow(unused)] + use crate::math::{chebyshev_tn, PI}; -use crate::{mat_eigen, mat_vec_mul, RootSolverBrent, StrError}; +use crate::StrError; +use crate::{mat_eigen, mat_vec_mul}; +use crate::{InterpGrid, InterpLagrange, InterpParams, RootSolverBrent}; use crate::{Matrix, Vector}; /// Tolerance to avoid division by zero on the trailing Chebyshev coefficient @@ -46,6 +50,82 @@ pub struct MultiRootSolverCheby { xb: f64, } +// returns `(yy, uu, cc)` +fn chebyshev_coefficients(nn: usize, xa: f64, xb: f64, args: &mut A, f: &mut F) -> (Vector, Vector, Vector) +where + F: FnMut(f64, &mut A) -> Result, +{ + assert!(xb > xa); + + // standard Chebyshev-Gauss-Lobatto coordinates + let yy = standard_chebyshev_lobatto_points(nn); + + // interpolation matrix + let nf = nn as f64; + let np = nn + 1; + let mut pp = Matrix::new(np, np); + for j in 0..np { + let jf = j as f64; + let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; + for k in 0..np { + let kf = k as f64; + let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; + pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); + } + } + + // data vector + let mut uu = Vector::new(np); + for k in 0..np { + let x = (xb + xa + (xb - xa) * yy[k]) / 2.0; + uu[k] = (*f)(x, args).unwrap(); + } + + // Chebyshev coefficients + let mut cc = Vector::new(np); + mat_vec_mul(&mut cc, 1.0, &pp, &uu).unwrap(); + (yy, uu, cc) +} + +fn chebyshev_interpolation(x: f64, xa: f64, xb: f64, cc: &Vector) -> f64 { + assert!(xb > xa); + let y = (2.0 * x - xb - xa) / (xb - xa); + let mut sum = 0.0; + for k in 0..cc.dim() { + sum += cc[k] * chebyshev_tn(k, y); + } + sum +} + +pub fn adaptive_interpolation( + nn_max: usize, + tolerance: f64, + xa: f64, + xb: f64, + args: &mut A, + mut f: F, +) -> Result +where + F: FnMut(f64, &mut A) -> Result, +{ + if nn_max > 1000 { + return Err("max N must be ≤ 1000"); + } + let (yy, uu, cc) = chebyshev_coefficients(1, xa, xb, args, &mut f); + let mut cn_prev = cc[1]; + println!("N = 1, cn = {}", cn_prev); + for nn in 2..nn_max { + let (yy, uu, cc) = chebyshev_coefficients(nn, xa, xb, args, &mut f); + let cn = cc[nn]; + println!("N = {}, cn = {}", nn, cn); + if f64::abs(cn_prev) < tolerance && f64::abs(cn) < tolerance { + return Ok(nn - 2); + } + cn_prev = cn; + } + Err("adaptive interpolation did not converge") +} + impl MultiRootSolverCheby { /// Allocates a new instance pub fn new(nn: usize) -> Result { @@ -70,7 +150,7 @@ impl MultiRootSolverCheby { pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); } } - println!("P = \n{:.5}", pp); + // println!("P = \n{:.5}", pp); // companion matrix (except last row) let mut aa = Matrix::new(nn, nn); @@ -114,7 +194,7 @@ impl MultiRootSolverCheby { // calculate the Chebyshev coefficients mat_vec_mul(&mut self.c, 1.0, &self.pp, &self.u).unwrap(); - println!("c = \n{:.5}", self.c); + // println!("c = \n{:.5}", self.c); // done self.all_data_set = true; @@ -162,7 +242,7 @@ impl MultiRootSolverCheby { self.aa.set(nn - 1, k, -0.5 * self.c[k] / cn); } self.aa.add(nn - 1, nn - 2, 0.5); - println!("A =\n{:.4}", self.aa); + // println!("A =\n{:.4}", self.aa); // eigenvalues let mut l_real = Vector::new(nn); @@ -171,8 +251,8 @@ impl MultiRootSolverCheby { let mut v_imag = Matrix::new(nn, nn); mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut self.aa).unwrap(); - println!("l_real =\n{}", l_real); - println!("l_imag =\n{}", l_imag); + // println!("l_real =\n{}", l_real); + // println!("l_imag =\n{}", l_imag); // roots = real eigenvalues within the interval let mut nroot = 0; @@ -269,13 +349,16 @@ fn standard_chebyshev_lobatto_points(nn: usize) -> Vector { #[cfg(test)] mod tests { - use super::MultiRootSolverCheby; + use super::{chebyshev_coefficients, chebyshev_interpolation, MultiRootSolverCheby}; + use crate::adaptive_interpolation; use crate::algo::NoArgs; use crate::array_approx_eq; use crate::get_test_functions; + use crate::math::PI; use crate::polish_roots; use crate::StrError; use crate::Vector; + use crate::{InterpGrid, InterpLagrange, InterpParams}; use plotpy::Legend; use plotpy::{Curve, Plot}; @@ -290,8 +373,7 @@ mod tests { roots_polished: &[f64], args: &mut A, mut f: F, - ) -> Plot - where + ) where F: FnMut(f64, &mut A) -> Result, { let xx = Vector::linspace(xa, xb, 101).unwrap(); @@ -302,8 +384,11 @@ mod tests { let mut zeros_unpolished = Curve::new(); let mut zeros_polished = Curve::new(); curve_ana.set_label("analytical"); - curve_int.set_label("interpolated"); - curve_int.set_line_style("--").set_marker_style(".").set_marker_every(5); + curve_int + .set_label("interpolated") + .set_line_style("--") + .set_marker_style(".") + .set_marker_every(5); zeros_unpolished .set_marker_style("o") .set_marker_void(true) @@ -337,17 +422,75 @@ mod tests { .grid_and_labels("x", "f(x)") .save(&format!("/tmp/russell/{}.svg", name)) .unwrap(); - plot + } + + #[test] + fn adaptive_interpolation_works() { + let mut f = |x: f64, _: &mut NoArgs| -> Result { + // Ok(0.0) + // Ok(x - 0.5) + // Ok(x * x - 1.0) + // Ok(x * x * x - 0.5) + // Ok(x * x * x * x - 0.5) + // Ok(x * x * x * x * x - 0.5) + // Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)) + // Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) + Ok(f64::ln(2.0 * f64::cos(x / 2.0))) + }; + // let (xa, xb) = (-1.0, 1.0); + // let (xa, xb) = (-2.34567, 12.34567); + let (xa, xb) = (-0.995 * PI, 0.995 * PI); + + let nn_max = 400; + let tol = 1e-8; + let args = &mut 0; + let nn = adaptive_interpolation(nn_max, tol, xa, xb, args, f).unwrap(); + println!("N = {}", nn); + + if SAVE_FIGURE { + let (yy, uu, cc) = chebyshev_coefficients(nn, xa, xb, args, &mut f); + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| chebyshev_interpolation(x, xa, xb, &cc)); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label("interpolated") + .set_line_style("--") + .set_marker_style(".") + .set_marker_every(5); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell/test_adaptive_interpolation.svg") + .unwrap(); + } } #[test] fn multi_root_solver_cheby_simple() { // function - let f = |x, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; - let (xa, xb) = (-4.0, 4.0); + let f = |x, _: &mut NoArgs| -> Result { + // Ok(x * x - 1.0) + Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) + // Ok(f64::ln(2.0 * f64::cos(x / 2.0))) + }; + // let (xa, xb) = (-4.0, 4.0); + let (xa, xb) = (-2.34567, 12.34567); + // let (xa, xb) = (-0.995 * PI, 0.995 * PI); // degree - let nn = 2; + let nn = 146; // solver let mut solver = MultiRootSolverCheby::new(nn).unwrap(); @@ -355,12 +498,15 @@ mod tests { // data let args = &mut 0; solver.set_data_from_function(xa, xb, args, f).unwrap(); - println!("U =\n{}", solver.u); + // println!("U =\n{}", solver.u); // find roots let roots_unpolished = Vec::from(solver.find().unwrap()); let mut roots_polished = vec![0.0; roots_unpolished.len()]; polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, f).unwrap(); + println!("n_roots = {}", roots_polished.len()); + println!("roots_unpolished = {:?}", roots_unpolished); + println!("roots_polished = {:?}", roots_polished); // figure if SAVE_FIGURE { @@ -377,7 +523,7 @@ mod tests { } // check - array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-12); + // array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-12); } #[test] From e374bef965af22fefd4124df2f3a68c57ee0313c Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Fri, 21 Jun 2024 14:59:29 +1000 Subject: [PATCH 16/93] Fix typo --- russell_lab/src/algo/interp_lagrange.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/russell_lab/src/algo/interp_lagrange.rs b/russell_lab/src/algo/interp_lagrange.rs index e8a4a207..4afd1668 100644 --- a/russell_lab/src/algo/interp_lagrange.rs +++ b/russell_lab/src/algo/interp_lagrange.rs @@ -498,7 +498,7 @@ impl InterpLagrange { /// /// # Input /// - /// * `x` -- the coordinate to evaluate the polynomial; must satisfy -1 ≤ j ≤ 1 + /// * `x` -- the coordinate to evaluate the polynomial; must satisfy -1 ≤ x ≤ 1 /// * `uu` -- the "data" vector `U` of size equal to `N + 1` /// /// # Examples From a5a0d27749f7d8052d3e836cd3f3b0b7267f6215 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Fri, 21 Jun 2024 15:14:34 +1000 Subject: [PATCH 17/93] [wip] Chebyshev interpolation --- .../src/algo/multi_root_solver_cheby.rs | 110 +++++++++++------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 725b6630..3c1d3a93 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,8 +1,8 @@ #![allow(unused)] use crate::math::{chebyshev_tn, PI}; -use crate::StrError; -use crate::{mat_eigen, mat_vec_mul}; +use crate::{approx_eq, array_approx_eq, mat_eigen, mat_vec_mul, Stopwatch}; +use crate::{vec_approx_eq, StrError}; use crate::{InterpGrid, InterpLagrange, InterpParams, RootSolverBrent}; use crate::{Matrix, Vector}; @@ -50,41 +50,46 @@ pub struct MultiRootSolverCheby { xb: f64, } -// returns `(yy, uu, cc)` -fn chebyshev_coefficients(nn: usize, xa: f64, xb: f64, args: &mut A, f: &mut F) -> (Vector, Vector, Vector) -where +// Chebyshev-Gauss-Lobatto +fn chebyshev_coefficients( + workspace_uu: &mut [f64], + workspace_cc: &mut [f64], + nn: usize, + xa: f64, + xb: f64, + args: &mut A, + f: &mut F, +) where F: FnMut(f64, &mut A) -> Result, { + // check + let np = nn + 1; + assert!(nn > 0); assert!(xb > xa); + assert!(workspace_uu.len() >= np); + assert!(workspace_cc.len() >= np); - // standard Chebyshev-Gauss-Lobatto coordinates - let yy = standard_chebyshev_lobatto_points(nn); - - // interpolation matrix + // data vector let nf = nn as f64; - let np = nn + 1; - let mut pp = Matrix::new(np, np); + let uu = &mut workspace_uu[0..np]; + for k in 0..np { + let kf = k as f64; + let x = (xb + xa + (xb - xa) * f64::cos(PI * kf / nf)) / 2.0; + uu[k] = (*f)(x, args).unwrap(); + } + + // coefficients + let cc = &mut workspace_cc[0..np]; for j in 0..np { let jf = j as f64; let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; + cc[j] = 0.0; for k in 0..np { let kf = k as f64; let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; - pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); + cc[j] += uu[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); } } - - // data vector - let mut uu = Vector::new(np); - for k in 0..np { - let x = (xb + xa + (xb - xa) * yy[k]) / 2.0; - uu[k] = (*f)(x, args).unwrap(); - } - - // Chebyshev coefficients - let mut cc = Vector::new(np); - mat_vec_mul(&mut cc, 1.0, &pp, &uu).unwrap(); - (yy, uu, cc) } fn chebyshev_interpolation(x: f64, xa: f64, xb: f64, cc: &Vector) -> f64 { @@ -108,15 +113,19 @@ pub fn adaptive_interpolation( where F: FnMut(f64, &mut A) -> Result, { - if nn_max > 1000 { - return Err("max N must be ≤ 1000"); + if nn_max > 2048 { + return Err("max N must be ≤ 2048"); } - let (yy, uu, cc) = chebyshev_coefficients(1, xa, xb, args, &mut f); - let mut cn_prev = cc[1]; - println!("N = 1, cn = {}", cn_prev); + let np_max = nn_max + 1; + let mut workspace_uu = vec![0.0; np_max]; + let mut workspace_cc = vec![0.0; np_max]; + let nn = 1; + chebyshev_coefficients(&mut workspace_uu, &mut workspace_cc, nn, xa, xb, args, &mut f); + let mut cn_prev = workspace_cc[nn]; + println!("N = {}, cn = {}", nn, cn_prev); for nn in 2..nn_max { - let (yy, uu, cc) = chebyshev_coefficients(nn, xa, xb, args, &mut f); - let cn = cc[nn]; + chebyshev_coefficients(&mut workspace_uu, &mut workspace_cc, nn, xa, xb, args, &mut f); + let cn = workspace_cc[nn]; println!("N = {}, cn = {}", nn, cn); if f64::abs(cn_prev) < tolerance && f64::abs(cn) < tolerance { return Ok(nn - 2); @@ -135,7 +144,8 @@ impl MultiRootSolverCheby { } // standard Chebyshev-Gauss-Lobatto coordinates - let yy = standard_chebyshev_lobatto_points(nn); + // let yy = standard_chebyshev_lobatto_points(nn); + let yy = Vector::new(nn + 1); // interpolation matrix let nf = nn as f64; @@ -322,12 +332,11 @@ where } /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates -fn standard_chebyshev_lobatto_points(nn: usize) -> Vector { - let mut yy = Vector::new(nn + 1); +fn standard_chebyshev_lobatto_points(yy: &mut [f64], nn: usize) { yy[0] = 1.0; yy[nn] = -1.0; if nn < 3 { - return yy; + return; } let nf = nn as f64; let d = 2.0 * nf; @@ -342,15 +351,14 @@ fn standard_chebyshev_lobatto_points(nn: usize) -> Vector { yy[nn - i] = -f64::sin(PI * (nf - 2.0 * (i as f64)) / d); yy[i] = -yy[nn - i]; } - yy } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::{chebyshev_coefficients, chebyshev_interpolation, MultiRootSolverCheby}; - use crate::adaptive_interpolation; + use super::adaptive_interpolation; + use super::{chebyshev_coefficients, MultiRootSolverCheby}; use crate::algo::NoArgs; use crate::array_approx_eq; use crate::get_test_functions; @@ -424,6 +432,18 @@ mod tests { .unwrap(); } + #[test] + fn todo_works() { + let mut f = |x: f64, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; + let (xa, xb) = (-1.0, 1.0); + let mut workspace_uu = vec![0.0; 2000]; + let mut workspace_cc = vec![0.0; 2000]; + let args = &mut 0; + for nn in 1000..1001 { + chebyshev_coefficients(&mut workspace_uu, &mut workspace_cc, nn, xa, xb, args, &mut f); + } + } + #[test] fn adaptive_interpolation_works() { let mut f = |x: f64, _: &mut NoArgs| -> Result { @@ -448,10 +468,20 @@ mod tests { println!("N = {}", nn); if SAVE_FIGURE { - let (yy, uu, cc) = chebyshev_coefficients(nn, xa, xb, args, &mut f); + let np = nn + 1; + let interp = InterpLagrange::new(nn, None).unwrap(); + let mut uu = Vector::new(np); + for (i, y) in interp.get_points().into_iter().enumerate() { + let x = (xb + xa + (xb - xa) * y) / 2.0; + uu[i] = f(x, args).unwrap(); + } let xx = Vector::linspace(xa, xb, 201).unwrap(); let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); - let yy_int = xx.get_mapped(|x| chebyshev_interpolation(x, xa, xb, &cc)); + let yy_int = xx.get_mapped(|x| { + let y = (2.0 * x - xb - xa) / (xb - xa); + let yy = f64::max(-1.0, f64::min(1.0, y)); + interp.eval(yy, &uu).unwrap() + }); let mut curve_ana = Curve::new(); let mut curve_int = Curve::new(); curve_ana.set_label("analytical"); From 2c066bbbcd30e76906901ab9c56b75ea5760d967 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Fri, 21 Jun 2024 16:45:24 +1000 Subject: [PATCH 18/93] [wip] InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 217 +++++++++++++++++++++++ russell_lab/src/algo/mod.rs | 2 + 2 files changed, 219 insertions(+) create mode 100644 russell_lab/src/algo/interp_chebyshev.rs diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs new file mode 100644 index 00000000..bf046644 --- /dev/null +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -0,0 +1,217 @@ +#![allow(unused)] + +use crate::math::{chebyshev_tn, PI}; +use crate::StrError; + +/// Defines the tolerance to make sure that the range [xa, xb] is not zero +const TOL_RANGE: f64 = 1.0e-8; + +/// Implements the Chebyshev interpolant and associated functions +/// +/// **Note:** Only Chebyshev-Gauss-Lobatto points are considered here. +/// +/// # References +/// +/// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in +/// Single Domains. Springer. 563p +/// +/// # Warning +/// +/// The grid points are sorted from +1 to -1; See the note below from Reference # 1 (page 86): +/// +/// "Note that the Chebyshev quadrature points as just defined are ordered +/// from right to left. This violates our general convention that quadrature points +/// are ordered from left to right (see Sect. 2.2.3). Virtually all of the classical +/// literature on Chebyshev spectral methods uses this reversed order. Therefore, +/// in the special case of the Chebyshev quadrature points we shall adhere to the +/// ordering convention that is widely used in the literature (and implemented +/// in the available software). We realize that our resolution of this dilemma +/// imposes upon the reader the task of mentally reversing the ordering of the +/// Chebyshev nodes whenever they are used in general formulas for orthogonal +/// polynomials." (Canuto, Hussaini, Quarteroni, Zang) +pub struct InterpChebyshev { + nn: usize, + np: usize, + xa: f64, + xb: f64, + dx: f64, + coef: Vec, + data: Vec, + constant_fx: f64, +} + +impl InterpChebyshev { + pub fn new(nn: usize, xa: f64, xb: f64) -> Result { + if xb <= xa + TOL_RANGE { + return Err("xb must be greater than xa + ϵ"); + } + let np = nn + 1; + Ok(InterpChebyshev { + nn, + np, + xa, + xb, + dx: xb - xa, + coef: vec![0.0; np], + data: vec![0.0; np], + constant_fx: 0.0, + }) + } + + pub fn calc_data(&mut self, args: &mut A, mut f: F) -> Result<(), StrError> + where + F: FnMut(f64, &mut A) -> Result, + { + if self.nn == 0 { + self.constant_fx = f((self.xa + self.xb) / 2.0, args)?; + } else { + chebyshev_coefficients(&mut self.coef, &mut self.data, self.nn, self.xa, self.xb, args, &mut f); + } + Ok(()) + } + + pub fn new_adapt(nn_max: usize, tol: f64, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result + where + F: FnMut(f64, &mut A) -> Result, + { + if nn_max > 2048 { + return Err("the maximum degree N must be ≤ 2048"); + } + let np_max = nn_max + 1; + let mut work_coef = vec![0.0; np_max]; + let mut work_data = vec![0.0; np_max]; + let mut an_prev = 0.0; + for nn in 1..=nn_max { + chebyshev_coefficients(&mut work_coef, &mut work_data, nn, xa, xb, args, &mut f); + let an = work_coef[nn]; + println!("N = {}, an = {}", nn, an); + if nn > 1 && f64::abs(an_prev) < tol && f64::abs(an) < tol { + let nn_final = nn - 2; // -2 because the last two coefficients are zero + let mut interp = InterpChebyshev::new(nn_final, xa, xb).unwrap(); + interp.calc_data(args, f)?; + return Ok(interp); + } + an_prev = an; + } + Err("adaptive interpolation did not converge") + } + + pub fn eval(&self, x: f64) -> f64 { + if self.nn == 0 { + return self.constant_fx; + } + let mut sum = 0.0; + for k in 0..self.np { + let y = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); + sum += self.coef[k] * chebyshev_tn(k, y); + } + sum + } +} + +/// Computes the Chebyshev-Gauss-Lobatto coefficients +pub(crate) fn chebyshev_coefficients( + work_coef: &mut [f64], + work_data: &mut [f64], + nn: usize, + xa: f64, + xb: f64, + args: &mut A, + f: &mut F, +) where + F: FnMut(f64, &mut A) -> Result, +{ + // check + let np = nn + 1; + assert!(nn > 0); + assert!(xb > xa); + assert!(work_coef.len() >= np); + assert!(work_data.len() >= np); + + // data vector + let nf = nn as f64; + let data = &mut work_data[0..np]; + for k in 0..np { + let kf = k as f64; + let x = (xb + xa + (xb - xa) * f64::cos(PI * kf / nf)) / 2.0; + data[k] = (*f)(x, args).unwrap(); + } + + // coefficients + let coef = &mut work_coef[0..np]; + for j in 0..np { + let jf = j as f64; + let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; + coef[j] = 0.0; + for k in 0..np { + let kf = k as f64; + let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; + coef[j] += data[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::InterpChebyshev; + use crate::math::PI; + use crate::StrError; + use crate::{NoArgs, Vector}; + use plotpy::{Curve, Legend, Plot}; + + const SAVE_FIGURE: bool = true; + + #[test] + fn adaptive_interpolation_works() { + let mut f = |x: f64, _: &mut NoArgs| -> Result { + // Ok(2.0) + // Ok(x - 0.5) + // Ok(x * x - 1.0) + // Ok(x * x * x - 0.5) + // Ok(x * x * x * x - 0.5) + // Ok(x * x * x * x * x - 0.5) + Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)) + // Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) + // Ok(f64::ln(2.0 * f64::cos(x / 2.0))) + }; + let (xa, xb) = (-1.0, 1.0); + // let (xa, xb) = (-2.34567, 12.34567); + // let (xa, xb) = (-0.995 * PI, 0.995 * PI); + + let nn_max = 400; + let tol = 1e-7; + let args = &mut 0; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); + println!("N = {}", interp.nn); + + if SAVE_FIGURE { + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x)); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label("interpolated") + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell/test_interp_chebyshev_new_adapt.svg") + .unwrap(); + } + } +} diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index 4c3979d8..b5a2de00 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -1,6 +1,7 @@ //! This module implements algorithms built from base, math, and vector-matrix routines mod common; +mod interp_chebyshev; mod interp_lagrange; mod linear_fitting; mod min_bracketing; @@ -12,6 +13,7 @@ mod quadrature; mod root_solver_brent; mod testing; pub use crate::algo::common::*; +pub use crate::algo::interp_chebyshev::*; pub use crate::algo::interp_lagrange::*; pub use crate::algo::linear_fitting::*; pub use crate::algo::min_bracketing::*; From 59e632108e0f471d8d6f08d7fc1ed352962ba778 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sat, 22 Jun 2024 09:32:26 +1000 Subject: [PATCH 19/93] Fix comment --- russell_lab/src/algo/interp_lagrange.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/russell_lab/src/algo/interp_lagrange.rs b/russell_lab/src/algo/interp_lagrange.rs index 4afd1668..2cad5e3f 100644 --- a/russell_lab/src/algo/interp_lagrange.rs +++ b/russell_lab/src/algo/interp_lagrange.rs @@ -960,7 +960,7 @@ impl InterpLagrange { /// /// # Output /// - /// * `err_f` -- is the max interpolation in `[-1, 1]` + /// * `err_f` -- is the max interpolation error in `[-1, 1]` pub fn estimate_max_error(&self, args: &mut A, mut f: F) -> Result where F: FnMut(f64, &mut A) -> Result, From da235c99143cd116fbd8460234bacf63ffdd1951 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sat, 22 Jun 2024 09:42:55 +1000 Subject: [PATCH 20/93] [wip] InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 263 +++++++++++++++-------- 1 file changed, 175 insertions(+), 88 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index bf046644..b3789f79 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -1,52 +1,79 @@ -#![allow(unused)] - use crate::math::{chebyshev_tn, PI}; use crate::StrError; +use crate::Vector; /// Defines the tolerance to make sure that the range [xa, xb] is not zero const TOL_RANGE: f64 = 1.0e-8; +// The grid points are sorted from +1 to -1; +// +// See the note below from Reference # 1 (page 86): +// +// "Note that the Chebyshev quadrature points as just defined are ordered +// from right to left. This violates our general convention that quadrature points +// are ordered from left to right (see Sect. 2.2.3). Virtually all of the classical +// literature on Chebyshev spectral methods uses this reversed order. Therefore, +// in the special case of the Chebyshev quadrature points we shall adhere to the +// ordering convention that is widely used in the literature (and implemented +// in the available software). We realize that our resolution of this dilemma +// imposes upon the reader the task of mentally reversing the ordering of the +// Chebyshev nodes whenever they are used in general formulas for orthogonal +// polynomials." (Canuto, Hussaini, Quarteroni, Zang) + /// Implements the Chebyshev interpolant and associated functions /// /// **Note:** Only Chebyshev-Gauss-Lobatto points are considered here. +/// Also, the coordinates are sorted from +1 to -1. /// /// # References /// /// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in /// Single Domains. Springer. 563p -/// -/// # Warning -/// -/// The grid points are sorted from +1 to -1; See the note below from Reference # 1 (page 86): -/// -/// "Note that the Chebyshev quadrature points as just defined are ordered -/// from right to left. This violates our general convention that quadrature points -/// are ordered from left to right (see Sect. 2.2.3). Virtually all of the classical -/// literature on Chebyshev spectral methods uses this reversed order. Therefore, -/// in the special case of the Chebyshev quadrature points we shall adhere to the -/// ordering convention that is widely used in the literature (and implemented -/// in the available software). We realize that our resolution of this dilemma -/// imposes upon the reader the task of mentally reversing the ordering of the -/// Chebyshev nodes whenever they are used in general formulas for orthogonal -/// polynomials." (Canuto, Hussaini, Quarteroni, Zang) pub struct InterpChebyshev { + /// Holds the polynomial degree nn: usize, + + /// Holds the number of points (= N + 1) np: usize, + + /// Holds the lower bound xa: f64, + + /// Holds the upper bound xb: f64, + + /// Holds the difference xb - xa dx: f64, + + /// Holds the expansion coefficients (Chebyshev-Gauss-Lobatto) coef: Vec, + + /// Holds the function evaluation at the Chebyshev-Gauss-Lobatto points data: Vec, + + /// Holds the constant y=c value for a zeroth-order function constant_fx: f64, } impl InterpChebyshev { - pub fn new(nn: usize, xa: f64, xb: f64) -> Result { + /// Allocates a new instance + /// + /// # Input + /// + /// * `nn` -- polynomial degree + /// * `xa` -- lower bound + /// * `xb` -- upper bound (> xa + ϵ) + /// * `args` -- extra arguments for the f(x) function + /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + pub fn new(nn: usize, xa: f64, xb: f64, args: &mut A, f: F) -> Result + where + F: FnMut(f64, &mut A) -> Result, + { if xb <= xa + TOL_RANGE { return Err("xb must be greater than xa + ϵ"); } let np = nn + 1; - Ok(InterpChebyshev { + let mut interp = InterpChebyshev { nn, np, xa, @@ -55,21 +82,21 @@ impl InterpChebyshev { coef: vec![0.0; np], data: vec![0.0; np], constant_fx: 0.0, - }) - } - - pub fn calc_data(&mut self, args: &mut A, mut f: F) -> Result<(), StrError> - where - F: FnMut(f64, &mut A) -> Result, - { - if self.nn == 0 { - self.constant_fx = f((self.xa + self.xb) / 2.0, args)?; - } else { - chebyshev_coefficients(&mut self.coef, &mut self.data, self.nn, self.xa, self.xb, args, &mut f); - } - Ok(()) + }; + interp.initialize(args, f)?; + Ok(interp) } + /// Allocates a new instance using adaptive interpolation + /// + /// # Input + /// + /// * `nn_max` -- maximum polynomial degree (≤ 2048) + /// * `tol` -- tolerance to truncate the Chebyshev series (e.g., 1e-8) + /// * `xa` -- lower bound + /// * `xb` -- upper bound (> xa + ϵ) + /// * `args` -- extra arguments for the f(x) function + /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. pub fn new_adapt(nn_max: usize, tol: f64, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result where F: FnMut(f64, &mut A) -> Result, @@ -82,35 +109,78 @@ impl InterpChebyshev { let mut work_data = vec![0.0; np_max]; let mut an_prev = 0.0; for nn in 1..=nn_max { - chebyshev_coefficients(&mut work_coef, &mut work_data, nn, xa, xb, args, &mut f); + chebyshev_coefficients(&mut work_coef, &mut work_data, nn, xa, xb, args, &mut f)?; let an = work_coef[nn]; - println!("N = {}, an = {}", nn, an); if nn > 1 && f64::abs(an_prev) < tol && f64::abs(an) < tol { let nn_final = nn - 2; // -2 because the last two coefficients are zero - let mut interp = InterpChebyshev::new(nn_final, xa, xb).unwrap(); - interp.calc_data(args, f)?; - return Ok(interp); + return Ok(InterpChebyshev::new(nn_final, xa, xb, args, f)?); } an_prev = an; } Err("adaptive interpolation did not converge") } - pub fn eval(&self, x: f64) -> f64 { + /// Evaluates the interpolated f(x) function + pub fn eval(&self, x: f64) -> Result { if self.nn == 0 { - return self.constant_fx; + return Ok(self.constant_fx); } let mut sum = 0.0; for k in 0..self.np { let y = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); sum += self.coef[k] * chebyshev_tn(k, y); } - sum + Ok(sum) + } + + /// Estimates the max error by comparing the interpolation against a reference solution + /// + /// **Note:** The error is calculated by the max abs difference between the interpolation + /// and the reference (e.g., analytical) solution at `nstation` discrete points. + /// + /// # Input + /// + /// * `nstation` -- number of stations (e.g, `10_000`) to compute `max(|f(x) - I{f}(x)|)` + /// * `args` -- extra arguments for the callback function + /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + /// + /// # Output + /// + /// * `err_f` -- is the max interpolation error in `[xa, xb]` + pub fn estimate_max_error(&self, nstation: usize, args: &mut A, mut f: F) -> Result + where + F: FnMut(f64, &mut A) -> Result, + { + let mut err_f = 0.0; + let stations = Vector::linspace(self.xa, self.xb, nstation).unwrap(); + for p in 0..nstation { + let fi = self.eval(stations[p])?; + err_f = f64::max(err_f, f64::abs(fi - f(stations[p], args)?)); + } + Ok(err_f) + } + + /// Returns the polynomial degree N + pub fn get_degree(&self) -> usize { + self.nn + } + + /// Calculates the coefficient and data vectors + fn initialize(&mut self, args: &mut A, mut f: F) -> Result<(), StrError> + where + F: FnMut(f64, &mut A) -> Result, + { + if self.nn == 0 { + self.constant_fx = f((self.xa + self.xb) / 2.0, args)?; + } else { + chebyshev_coefficients(&mut self.coef, &mut self.data, self.nn, self.xa, self.xb, args, &mut f)?; + } + Ok(()) } } /// Computes the Chebyshev-Gauss-Lobatto coefficients -pub(crate) fn chebyshev_coefficients( +fn chebyshev_coefficients( work_coef: &mut [f64], work_data: &mut [f64], nn: usize, @@ -118,7 +188,8 @@ pub(crate) fn chebyshev_coefficients( xb: f64, args: &mut A, f: &mut F, -) where +) -> Result<(), StrError> +where F: FnMut(f64, &mut A) -> Result, { // check @@ -134,7 +205,7 @@ pub(crate) fn chebyshev_coefficients( for k in 0..np { let kf = k as f64; let x = (xb + xa + (xb - xa) * f64::cos(PI * kf / nf)) / 2.0; - data[k] = (*f)(x, args).unwrap(); + data[k] = (*f)(x, args)?; } // coefficients @@ -149,6 +220,7 @@ pub(crate) fn chebyshev_coefficients( coef[j] += data[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); } } + Ok(()) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -164,54 +236,69 @@ mod tests { const SAVE_FIGURE: bool = true; #[test] - fn adaptive_interpolation_works() { - let mut f = |x: f64, _: &mut NoArgs| -> Result { - // Ok(2.0) - // Ok(x - 0.5) - // Ok(x * x - 1.0) - // Ok(x * x * x - 0.5) - // Ok(x * x * x * x - 0.5) - // Ok(x * x * x * x * x - 0.5) - Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)) - // Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) - // Ok(f64::ln(2.0 * f64::cos(x / 2.0))) - }; - let (xa, xb) = (-1.0, 1.0); - // let (xa, xb) = (-2.34567, 12.34567); - // let (xa, xb) = (-0.995 * PI, 0.995 * PI); - + fn interp_chebyshev_new_adapt_works() { + let functions = [ + |_: f64, _: &mut NoArgs| -> Result { Ok(2.0) }, + |x: f64, _: &mut NoArgs| -> Result { Ok(x - 0.5) }, + |x: f64, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }, + |x: f64, _: &mut NoArgs| -> Result { Ok(x * x * x - 0.5) }, + |x: f64, _: &mut NoArgs| -> Result { Ok(x * x * x * x - 0.5) }, + |x: f64, _: &mut NoArgs| -> Result { Ok(x * x * x * x * x - 0.5) }, + |x: f64, _: &mut NoArgs| -> Result { + Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)) + }, + |x: f64, _: &mut NoArgs| -> Result { Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) }, + |x: f64, _: &mut NoArgs| -> Result { Ok(f64::ln(2.0 * f64::cos(x / 2.0))) }, + ]; + let ranges = [ + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-2.34567, 12.34567), + (-0.995 * PI, 0.995 * PI), + ]; let nn_max = 400; let tol = 1e-7; let args = &mut 0; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); - println!("N = {}", interp.nn); - - if SAVE_FIGURE { - let xx = Vector::linspace(xa, xb, 201).unwrap(); - let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); - let yy_int = xx.get_mapped(|x| interp.eval(x)); - let mut curve_ana = Curve::new(); - let mut curve_int = Curve::new(); - curve_ana.set_label("analytical"); - curve_int - .set_label("interpolated") - .set_line_style(":") - .set_marker_style(".") - .set_marker_every(5); - curve_ana.draw(xx.as_data(), yy_ana.as_data()); - curve_int.draw(xx.as_data(), yy_int.as_data()); - let mut plot = Plot::new(); - let mut legend = Legend::new(); - legend.set_num_col(4); - legend.set_outside(true); - legend.draw(); - plot.add(&curve_ana) - .add(&curve_int) - .add(&legend) - .set_cross(0.0, 0.0, "gray", "-", 1.5) - .grid_and_labels("x", "f(x)") - .save("/tmp/russell/test_interp_chebyshev_new_adapt.svg") - .unwrap(); + for (index, f) in functions.into_iter().enumerate() { + let (xa, xb) = ranges[index]; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); + let nn = interp.get_degree(); + println!("{:0>3} : N = {}", index, nn); + if SAVE_FIGURE { + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save(&format!( + "/tmp/russell/test_interp_chebyshev_new_adapt_{:0>3}.svg", + index + )) + .unwrap(); + } } } } From 5a03b3002e7e93817fd4907ef2c5195874b69284 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Sat, 22 Jun 2024 10:21:05 +1000 Subject: [PATCH 21/93] [wip] Improve test --- russell_lab/src/algo/interp_chebyshev.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index b3789f79..6dda44c8 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -233,7 +233,7 @@ mod tests { use crate::{NoArgs, Vector}; use plotpy::{Curve, Legend, Plot}; - const SAVE_FIGURE: bool = true; + const SAVE_FIGURE: bool = false; #[test] fn interp_chebyshev_new_adapt_works() { @@ -261,6 +261,7 @@ mod tests { (-2.34567, 12.34567), (-0.995 * PI, 0.995 * PI), ]; + let err_tols = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-6, 1e-6, 1e-6]; let nn_max = 400; let tol = 1e-7; let args = &mut 0; @@ -268,7 +269,9 @@ mod tests { let (xa, xb) = ranges[index]; let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); let nn = interp.get_degree(); - println!("{:0>3} : N = {}", index, nn); + let err = interp.estimate_max_error(1000, args, f).unwrap(); + println!("{:0>3} : N = {:>3} : err = {:.2e}", index, nn, err); + assert!(err <= err_tols[index]); if SAVE_FIGURE { let xx = Vector::linspace(xa, xb, 201).unwrap(); let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); From 86fd402437911568937b260187297f89dd43c3e6 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 11:31:30 +1000 Subject: [PATCH 22/93] Improve comments --- russell_lab/src/algo/interp_chebyshev.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 6dda44c8..81eba582 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -22,8 +22,14 @@ const TOL_RANGE: f64 = 1.0e-8; /// Implements the Chebyshev interpolant and associated functions /// -/// **Note:** Only Chebyshev-Gauss-Lobatto points are considered here. -/// Also, the coordinates are sorted from +1 to -1. +/// # Notes +/// +/// 1. This structure is meant for interpolating data and finding (all) the roots of an equation. +/// On the other hand, [crate::InterpLagrange] is meant for implementing spectral methods +/// for solving partial differential equations. Therefore, [crate::InterpLagrange] implements +/// the derivative matrices, whereas this structure does not. +/// 2. Only Chebyshev-Gauss-Lobatto points are considered here. +/// 3. The Chebyshev-Gauss-Lobatto coordinates are sorted from +1 to -1 (as in Reference # 1). /// /// # References /// From ef3bacc7815fc73cb151f88d499c850330f341ae Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 11:43:16 +1000 Subject: [PATCH 23/93] Add example --- russell_lab/README.md | 4 +- ...lagrange.svg => algo_interp_chebyshev.svg} | 908 +++++----- .../data/figures/algo_interp_lagrange.svg | 1550 +++++++++++++++++ russell_lab/examples/algo_interp_chebyshev.rs | 41 + ...on_lagrange.rs => algo_interp_lagrange.rs} | 25 +- russell_lab/src/algo/interp_chebyshev.rs | 28 +- 6 files changed, 2026 insertions(+), 530 deletions(-) rename russell_lab/data/figures/{algo_interpolation_lagrange.svg => algo_interp_chebyshev.svg} (63%) create mode 100644 russell_lab/data/figures/algo_interp_lagrange.svg create mode 100644 russell_lab/examples/algo_interp_chebyshev.rs rename russell_lab/examples/{algo_interpolation_lagrange.rs => algo_interp_lagrange.rs} (55%) diff --git a/russell_lab/README.md b/russell_lab/README.md index cfec5848..31b09e8c 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -328,11 +328,11 @@ Results: This example illustrates the use of `InterpLagrange` with at Chebyshev-Gauss-Lobatto grid to interpolate Runge's equation. -[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_interpolation_lagrange.rs) +[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_interp_lagrange.rs) Results: -![algo_interpolation_lagrange](data/figures/algo_interpolation_lagrange.svg) +![algo_interp_lagrange](data/figures/algo_interp_lagrange.svg) diff --git a/russell_lab/data/figures/algo_interpolation_lagrange.svg b/russell_lab/data/figures/algo_interp_chebyshev.svg similarity index 63% rename from russell_lab/data/figures/algo_interpolation_lagrange.svg rename to russell_lab/data/figures/algo_interp_chebyshev.svg index dbf85aac..ea45a5bb 100644 --- a/russell_lab/data/figures/algo_interpolation_lagrange.svg +++ b/russell_lab/data/figures/algo_interp_chebyshev.svg @@ -6,7 +6,7 @@ - 2024-04-20T13:46:22.910651 + 2024-06-24T11:40:10.533839 image/svg+xml @@ -42,16 +42,16 @@ z +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - - + @@ -120,11 +120,11 @@ z +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -179,11 +179,11 @@ z +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -201,11 +201,11 @@ L 141.177614 22.318125 +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -249,11 +249,11 @@ z +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -270,11 +270,11 @@ L 222.34125 22.318125 +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -291,11 +291,11 @@ L 262.923068 22.318125 +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -312,11 +312,11 @@ L 303.504886 22.318125 +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -333,11 +333,11 @@ L 344.086705 22.318125 +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -377,23 +377,23 @@ z - + - - + - + @@ -402,18 +402,18 @@ L -3.5 0 - + - + - + - + - + - + - + - + - + +" clip-path="url(#pc459af4f27)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -634,165 +634,277 @@ z - +L 225.587795 36.039554 +L 228.834341 40.794124 +L 232.080886 48.334781 +L 235.327432 58.156241 +L 238.573977 69.66912 +L 241.820523 82.276563 +L 245.067068 95.434037 +L 248.313614 108.685715 +L 251.560159 121.678596 +L 254.806705 134.159964 +L 258.05325 145.964871 +L 261.299795 156.999353 +L 264.546341 167.223295 +L 267.792886 176.635126 +L 271.039432 185.259267 +L 274.285977 193.13643 +L 277.532523 200.31649 +L 280.779068 206.853444 +L 284.025614 212.802013 +L 287.272159 218.215446 +L 290.518705 223.144191 +L 293.76525 227.635174 +L 297.011795 231.731482 +L 300.258341 235.472313 +L 303.504886 238.893095 +L 306.751432 242.025696 +L 309.997977 244.89869 +L 313.244523 247.537646 +L 316.491068 249.965408 +L 319.737614 252.202377 +L 322.984159 254.266766 +L 326.230705 256.174838 +L 329.47725 257.941126 +L 332.723795 259.578622 +L 335.970341 261.098956 +L 339.216886 262.512546 +L 342.463432 263.828737 +L 345.709977 265.055925 +L 348.956523 266.201659 +L 352.203068 267.272738 +L 355.449614 268.275296 +L 358.696159 269.214869 +L 361.942705 270.096468 +L 365.18925 270.924631 +L 368.435795 271.703474 +L 371.682341 272.436735 +L 374.928886 273.127815 +L 378.175432 273.779813 +L 381.421977 274.395554 +L 384.668523 274.977619 +" clip-path="url(#pc459af4f27)" style="fill: none; stroke: #1f77b4; stroke-width: 2; stroke-linecap: square"/> - + - +" style="stroke: #ff7f0e"/> - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -816,8 +928,8 @@ L 400.90125 22.318125 " style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/> - - + + - - - + + + + + + + + + + - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1245,9 +1118,9 @@ z +" style="fill: none; stroke-dasharray: 5.55,2.4; stroke-dashoffset: 0; stroke: #ff7f0e; stroke-width: 1.5"/> - + @@ -1328,6 +1201,27 @@ Q 1681 391 2138 391 Q 2594 391 2855 752 Q 3116 1113 3116 1747 z +" transform="scale(0.015625)"/> + + diff --git a/russell_lab/data/figures/algo_interp_lagrange.svg b/russell_lab/data/figures/algo_interp_lagrange.svg new file mode 100644 index 00000000..8382c0c6 --- /dev/null +++ b/russell_lab/data/figures/algo_interp_lagrange.svg @@ -0,0 +1,1550 @@ + + + + + + + + 2024-06-24T11:42:24.114831 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/examples/algo_interp_chebyshev.rs b/russell_lab/examples/algo_interp_chebyshev.rs new file mode 100644 index 00000000..1df15192 --- /dev/null +++ b/russell_lab/examples/algo_interp_chebyshev.rs @@ -0,0 +1,41 @@ +use plotpy::{Curve, Plot}; +use russell_lab::*; + +fn main() -> Result<(), StrError> { + // function + let f = |x, _: &mut NoArgs| Ok(1.0 / (1.0 + 16.0 * x * x)); + let (xa, xb) = (-1.0, 1.0); + + // interpolant + let degree = 10; + let args = &mut 0; + let interp = InterpChebyshev::new(degree, xa, xb, args, f)?; + approx_eq(interp.eval(0.0).unwrap(), 1.0, 1e-15); + + // plot + let xx = Vector::linspace(xa, xb, 101).unwrap(); + let y_original = xx.get_mapped(|x| f(x, args).unwrap()); + let y_approx = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve1 = Curve::new(); + let mut curve2 = Curve::new(); + curve1 + .set_label("exact") + .set_line_width(2.0) + .draw(xx.as_data(), y_original.as_data()); + curve2 + .set_label("interpolated") + .set_line_style("--") + .set_marker_style(".") + .set_marker_every(2) + .draw(xx.as_data(), y_approx.as_data()); + let mut plot = Plot::new(); + let path = "/tmp/russell_lab/algo_interp_chebyshev.svg"; + plot.add(&curve1) + .add(&curve2) + .legend() + .set_title(&format!("N = {}", degree)) + .grid_and_labels("$x$", "$f(x)$") + .save(path) + .unwrap(); + Ok(()) +} diff --git a/russell_lab/examples/algo_interpolation_lagrange.rs b/russell_lab/examples/algo_interp_lagrange.rs similarity index 55% rename from russell_lab/examples/algo_interpolation_lagrange.rs rename to russell_lab/examples/algo_interp_lagrange.rs index 6d94d939..d0eca158 100644 --- a/russell_lab/examples/algo_interpolation_lagrange.rs +++ b/russell_lab/examples/algo_interp_lagrange.rs @@ -1,12 +1,6 @@ use plotpy::{Curve, Plot}; use russell_lab::*; -// Runge equation -// Reference: -// * Gourgoulhon E (2005), An introduction to polynomial interpolation, -// School on spectral methods: Application to General Relativity and Field Theory -// Meudon, 14-18 November 2005 - fn main() -> Result<(), StrError> { // function let f = |x| 1.0 / (1.0 + 16.0 * x * x); @@ -22,29 +16,24 @@ fn main() -> Result<(), StrError> { uu[i] = f(*x); } - // stations for plotting - let nstation = 20; - let station = Vector::linspace(-1.0, 1.0, nstation).unwrap(); - // plot - let x_original = Vector::linspace(-1.0, 1.0, 101).unwrap(); - let y_original = x_original.get_mapped(|x| f(x)); - let y_approx = station.get_mapped(|x| interp.eval(x, &uu).unwrap()); + let xx = Vector::linspace(-1.0, 1.0, 101).unwrap(); + let y_original = xx.get_mapped(|x| f(x)); + let y_approx = xx.get_mapped(|x| interp.eval(x, &uu).unwrap()); let mut curve1 = Curve::new(); let mut curve2 = Curve::new(); curve1 .set_label("exact") .set_line_width(2.0) - .draw(x_original.as_data(), y_original.as_data()); + .draw(xx.as_data(), y_original.as_data()); curve2 .set_label("interpolated") .set_line_style("--") - .set_marker_style("o") - .set_marker_void(true) - .draw(station.as_data(), y_approx.as_data()); + .set_marker_style(".") + .draw(xx.as_data(), y_approx.as_data()); let mut plot = Plot::new(); let grid = format!("{:?}", interp.get_grid_type()); - let path = "/tmp/russell_lab/algo_interpolation_lagrange.svg"; + let path = "/tmp/russell_lab/algo_interp_lagrange.svg"; plot.add(&curve1) .add(&curve2) .legend() diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 81eba582..86117ec7 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -28,8 +28,9 @@ const TOL_RANGE: f64 = 1.0e-8; /// On the other hand, [crate::InterpLagrange] is meant for implementing spectral methods /// for solving partial differential equations. Therefore, [crate::InterpLagrange] implements /// the derivative matrices, whereas this structure does not. -/// 2. Only Chebyshev-Gauss-Lobatto points are considered here. -/// 3. The Chebyshev-Gauss-Lobatto coordinates are sorted from +1 to -1 (as in Reference # 1). +/// 2. The [crate::InterpLagrange] renders the same results when using Chebyshev-Gauss-Lobatto points. +/// 3. Only Chebyshev-Gauss-Lobatto points are considered here. +/// 4. The Chebyshev-Gauss-Lobatto coordinates are sorted from +1 to -1 (as in Reference # 1). /// /// # References /// @@ -71,6 +72,27 @@ impl InterpChebyshev { /// * `xb` -- upper bound (> xa + ϵ) /// * `args` -- extra arguments for the f(x) function /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + /// + /// # Examples + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// // function + /// let f = |x, _: &mut NoArgs| Ok(1.0 / (1.0 + 16.0 * x * x)); + /// let (xa, xb) = (-1.0, 1.0); + /// + /// // interpolant + /// let degree = 10; + /// let args = &mut 0; + /// let interp = InterpChebyshev::new(degree, xa, xb, args, f)?; + /// + /// // check + /// approx_eq(interp.eval(0.0).unwrap(), 1.0, 1e-15); + /// Ok(()) + /// } + /// ``` pub fn new(nn: usize, xa: f64, xb: f64, args: &mut A, f: F) -> Result where F: FnMut(f64, &mut A) -> Result, @@ -303,7 +325,7 @@ mod tests { .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") .save(&format!( - "/tmp/russell/test_interp_chebyshev_new_adapt_{:0>3}.svg", + "/tmp/russell_lab/test_interp_chebyshev_new_adapt_{:0>3}.svg", index )) .unwrap(); From 6f1c8872ba1afbc22e849f38954d9dc333f187d9 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 11:44:05 +1000 Subject: [PATCH 24/93] Simplify test --- russell_lab/src/algo/interp_chebyshev.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 86117ec7..5051b3ee 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -257,7 +257,6 @@ where mod tests { use super::InterpChebyshev; use crate::math::PI; - use crate::StrError; use crate::{NoArgs, Vector}; use plotpy::{Curve, Legend, Plot}; @@ -266,17 +265,15 @@ mod tests { #[test] fn interp_chebyshev_new_adapt_works() { let functions = [ - |_: f64, _: &mut NoArgs| -> Result { Ok(2.0) }, - |x: f64, _: &mut NoArgs| -> Result { Ok(x - 0.5) }, - |x: f64, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }, - |x: f64, _: &mut NoArgs| -> Result { Ok(x * x * x - 0.5) }, - |x: f64, _: &mut NoArgs| -> Result { Ok(x * x * x * x - 0.5) }, - |x: f64, _: &mut NoArgs| -> Result { Ok(x * x * x * x * x - 0.5) }, - |x: f64, _: &mut NoArgs| -> Result { - Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)) - }, - |x: f64, _: &mut NoArgs| -> Result { Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) }, - |x: f64, _: &mut NoArgs| -> Result { Ok(f64::ln(2.0 * f64::cos(x / 2.0))) }, + |_: f64, _: &mut NoArgs| Ok(2.0), + |x: f64, _: &mut NoArgs| Ok(x - 0.5), + |x: f64, _: &mut NoArgs| Ok(x * x - 1.0), + |x: f64, _: &mut NoArgs| Ok(x * x * x - 0.5), + |x: f64, _: &mut NoArgs| Ok(x * x * x * x - 0.5), + |x: f64, _: &mut NoArgs| Ok(x * x * x * x * x - 0.5), + |x: f64, _: &mut NoArgs| Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)), + |x: f64, _: &mut NoArgs| Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)), + |x: f64, _: &mut NoArgs| Ok(f64::ln(2.0 * f64::cos(x / 2.0))), ]; let ranges = [ (-1.0, 1.0), From 128799976f2e37d4bb44f9b8f661e8d27ffd1203 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 11:47:24 +1000 Subject: [PATCH 25/93] Add example --- .../figures/algo_interp_chebyshev_adapt.svg | 1282 +++++++++++++++++ .../examples/algo_interp_chebyshev_adapt.rs | 42 + 2 files changed, 1324 insertions(+) create mode 100644 russell_lab/data/figures/algo_interp_chebyshev_adapt.svg create mode 100644 russell_lab/examples/algo_interp_chebyshev_adapt.rs diff --git a/russell_lab/data/figures/algo_interp_chebyshev_adapt.svg b/russell_lab/data/figures/algo_interp_chebyshev_adapt.svg new file mode 100644 index 00000000..cd22ffb7 --- /dev/null +++ b/russell_lab/data/figures/algo_interp_chebyshev_adapt.svg @@ -0,0 +1,1282 @@ + + + + + + + + 2024-06-24T11:46:32.215706 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/examples/algo_interp_chebyshev_adapt.rs b/russell_lab/examples/algo_interp_chebyshev_adapt.rs new file mode 100644 index 00000000..93af8f26 --- /dev/null +++ b/russell_lab/examples/algo_interp_chebyshev_adapt.rs @@ -0,0 +1,42 @@ +use plotpy::{Curve, Plot}; +use russell_lab::*; + +fn main() -> Result<(), StrError> { + // function + let f = |x, _: &mut NoArgs| Ok(1.0 / (1.0 + 16.0 * x * x)); + let (xa, xb) = (-1.0, 1.0); + + // interpolant + let nn_max = 200; + let tol = 1e-8; + let args = &mut 0; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f)?; + println!("N = {}", interp.get_degree()); + + // plot + let xx = Vector::linspace(xa, xb, 101).unwrap(); + let y_original = xx.get_mapped(|x| f(x, args).unwrap()); + let y_approx = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve1 = Curve::new(); + let mut curve2 = Curve::new(); + curve1 + .set_label("exact") + .set_line_width(2.0) + .draw(xx.as_data(), y_original.as_data()); + curve2 + .set_label("interpolated") + .set_line_style("--") + .set_marker_style(".") + .set_marker_every(2) + .draw(xx.as_data(), y_approx.as_data()); + let mut plot = Plot::new(); + let path = "/tmp/russell_lab/algo_interp_chebyshev_adapt.svg"; + plot.add(&curve1) + .add(&curve2) + .legend() + .set_title(&format!("N = {}", interp.get_degree())) + .grid_and_labels("$x$", "$f(x)$") + .save(path) + .unwrap(); + Ok(()) +} From e7b6b0d91ab3e466b35e1807e81ad85ae872c3e3 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 11:49:13 +1000 Subject: [PATCH 26/93] Add example --- russell_lab/src/algo/interp_chebyshev.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 5051b3ee..22681833 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -125,6 +125,28 @@ impl InterpChebyshev { /// * `xb` -- upper bound (> xa + ϵ) /// * `args` -- extra arguments for the f(x) function /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + /// + /// # Examples + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// // function + /// let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); + /// let (xa, xb) = (-1.0, 1.0); + /// + /// // interpolant + /// let nn_max = 200; + /// let tol = 1e-8; + /// let args = &mut 0; + /// let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f)?; + /// + /// // check + /// assert_eq!(interp.get_degree(), 2); + /// Ok(()) + /// } + /// ``` pub fn new_adapt(nn_max: usize, tol: f64, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result where F: FnMut(f64, &mut A) -> Result, From 1d51e3f640e8737c5e43b0a83e80fea586120d0a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 12:34:10 +1000 Subject: [PATCH 27/93] [math] Impl function to return standard Chebyshev-Gauss-Lobatto coordinates --- russell_lab/src/math/chebyshev.rs | 235 ++++++++++++++++++++++++++++-- 1 file changed, 225 insertions(+), 10 deletions(-) diff --git a/russell_lab/src/math/chebyshev.rs b/russell_lab/src/math/chebyshev.rs index 9c1982eb..38e21805 100644 --- a/russell_lab/src/math/chebyshev.rs +++ b/russell_lab/src/math/chebyshev.rs @@ -244,24 +244,24 @@ pub fn chebyshev_gauss_points(nn: usize) -> Vector { xx } -/// Computes Chebyshev-Gauss-Lobatto points +/// Returns the Chebyshev-Gauss-Lobatto points (from -1 to 1) /// -/// The point coordinates are given by (using the sin(x) function by default): +/// The point coordinates are defined by: /// /// ```text -/// ⎛ π⋅(N-2j) ⎞ -/// Xⱼ = -sin ⎜ —————————— ⎟ -/// ⎝ 2⋅N ⎠ +/// ⎛ j⋅π ⎞ +/// Xⱼ = -cos ⎜ ————— ⎟ +/// ⎝ N ⎠ /// /// j = 0 ... N /// ``` /// -/// Another option is: +/// Nonetheless, here, the coordinates are calculates by using the sin(x) function: /// /// ```text -/// ⎛ j⋅π ⎞ -/// Xⱼ = -cos ⎜ ————— ⎟ -/// ⎝ N ⎠ +/// ⎛ π⋅(N-2j) ⎞ +/// Xⱼ = -sin ⎜ —————————— ⎟ +/// ⎝ 2⋅N ⎠ /// /// j = 0 ... N /// ``` @@ -279,6 +279,10 @@ pub fn chebyshev_gauss_points(nn: usize) -> Vector { /// /// **Note:** This function enforces the symmetry of the sequence of points. /// +/// # Input +/// +/// * `nn` -- the polynomial degree `N`; thus the number of points is `npoint = nn + 1`. +/// /// # References /// /// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in @@ -326,11 +330,114 @@ pub fn chebyshev_lobatto_points(nn: usize) -> Vector { xx } +/// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates +/// +/// The point coordinates are defined by: +/// +/// ```text +/// ⎛ j⋅π ⎞ +/// Xⱼ = cos ⎜ ————— ⎟ +/// ⎝ N ⎠ +/// +/// j = 0 ... N +/// ``` +/// +/// Nonetheless, here, the coordinates are calculates by using the sin(x) function: +/// +/// ```text +/// ⎛ π⋅(N-2j) ⎞ +/// Xⱼ = sin ⎜ —————————— ⎟ +/// ⎝ 2⋅N ⎠ +/// +/// j = 0 ... N +/// ``` +/// +/// See equation (2.4.14) on page 86 of the Reference #1 (with the "standard" ordering of nodes from +1 to -1). +/// See also equation (1) of the Reference #2 +/// +/// **Note:** This function enforces the symmetry of the sequence of points. +/// +/// # Input +/// +/// * `nn` -- the polynomial degree `N`; thus the number of points is `npoint = nn + 1`. +/// +/// # Input +/// +/// * The number of points will be equal to `npoint = yy.len()` and the +/// polynomial degree will be equal to `nn = npoint - 1` +/// +/// # Output +/// +/// * `yy` -- is the array holding the coordinates (from 1 to -1). Its length is equal +/// to `npoint = nn + 1` where `nn` is the polynomial degree. Requirement: `npoint ≥ 2`. +/// +/// # Notes +/// +/// See the note below from Reference # 1 (page 86): +/// +/// "Note that the Chebyshev quadrature points as just defined are ordered +/// from right to left. This violates our general convention that quadrature points +/// are ordered from left to right (see Sect. 2.2.3). Virtually all of the classical +/// literature on Chebyshev spectral methods uses this reversed order. Therefore, +/// in the special case of the Chebyshev quadrature points we shall adhere to the +/// ordering convention that is widely used in the literature (and implemented +/// in the available software). We realize that our resolution of this dilemma +/// imposes upon the reader the task of mentally reversing the ordering of the +/// Chebyshev nodes whenever they are used in general formulas for orthogonal +/// polynomials." (Canuto, Hussaini, Quarteroni, Zang) +/// +/// # References +/// +/// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in +/// Single Domains. Springer. 563p +/// 2. Baltensperger R and Trummer MR (2003) Spectral differencing with a twist, +/// SIAM Journal Scientific Computation 24(5):1465-1487 +/// +/// # Examples +/// +/// ![Chebyshev points](https://raw.githubusercontent.com/cpmech/russell/main/russell_lab/data/figures/math_chebyshev_points.svg) +/// +/// ``` +/// use russell_lab::math; +/// +/// let xx = math::chebyshev_lobatto_points_standard(8); +/// println!("\nChebyshev-Gauss-Lobatto points =\n{:.3?}\n", xx.as_data()); +/// ``` +/// +/// The output looks like: +/// +/// ```text +/// Chebyshev-Gauss-Lobatto points = +/// [1.000, 0.924, 0.707, 0.383, 0.000, -0.383, -0.707, -0.924, -1.000] +/// ``` +pub fn chebyshev_lobatto_points_standard(nn: usize) -> Vector { + let mut xx = Vector::new(nn + 1); + xx[0] = 1.0; + xx[nn] = -1.0; + if nn < 3 { + return xx; + } + let nf = nn as f64; + let d = 2.0 * nf; + let l = if (nn & 1) == 0 { + // even number of segments + nn / 2 + } else { + // odd number of segments + (nn + 3) / 2 - 1 + }; + for i in 1..l { + xx[nn - i] = -f64::sin(PI * (nf - 2.0 * (i as f64)) / d); + xx[i] = -xx[nn - i]; + } + return xx; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::{chebyshev_gauss_points, chebyshev_lobatto_points}; + use super::{chebyshev_gauss_points, chebyshev_lobatto_points, chebyshev_lobatto_points_standard}; use super::{chebyshev_tn, chebyshev_tn_deriv1, chebyshev_tn_deriv2}; use crate::math::{SQRT_2, SQRT_3}; use crate::{approx_eq, vec_approx_eq, Vector}; @@ -427,6 +534,9 @@ mod tests { #[test] fn chebyshev_gauss_points_works() { + let xx = chebyshev_gauss_points(0); + assert_eq!(xx.as_data(), &[0.0]); + let xx = chebyshev_gauss_points(1); let xx_ref = vec![-1.0 / SQRT_2, 1.0 / SQRT_2]; vec_approx_eq(&xx, &xx_ref, 1e-15); @@ -531,6 +641,9 @@ mod tests { #[test] fn chebyshev_lobatto_points_works() { + let xx = chebyshev_lobatto_points(0); + assert_eq!(xx.as_data(), &[1.0]); + let xx = chebyshev_lobatto_points(1); let xx_ref = vec![-1.0, 1.0]; vec_approx_eq(&xx, &xx_ref, 1e-15); @@ -627,4 +740,106 @@ mod tests { vec_approx_eq(&xx, &xx_ref, 1e-15); check_segment_symmetry(&xx); } + + #[test] + fn chebyshev_lobatto_points_standard_works() { + let xx = chebyshev_lobatto_points_standard(0); + assert_eq!(xx.as_data(), &[-1.0]); + + let xx = chebyshev_lobatto_points_standard(1); + let xx_ref = vec![1.0, -1.0]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(2); + let xx_ref = vec![1.0, 0.0, -1.0]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(3); + let xx_ref = vec![1.0, 0.5, -0.5, -1.0]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(4); + let xx_ref = vec![ + 1.000000000000000e+00, + 7.071067811865476e-01, + 0.0, + -7.071067811865475e-01, + -1.000000000000000e+00, + ]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(5); + let xx_ref = vec![ + 1.000000000000000e+00, + 8.090169943749475e-01, + 3.090169943749475e-01, + -3.090169943749473e-01, + -8.090169943749473e-01, + -1.000000000000000e+00, + ]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(6); + let xx_ref = vec![ + 1.000000000000000e+00, + 8.660254037844387e-01, + 5.000000000000001e-01, + 0.0, + -4.999999999999998e-01, + -8.660254037844385e-01, + -1.000000000000000e+00, + ]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(7); + let xx_ref = vec![ + 1.000000000000000e+00, + 9.009688679024191e-01, + 6.234898018587336e-01, + 2.225209339563144e-01, + -2.225209339563143e-01, + -6.234898018587335e-01, + -9.009688679024190e-01, + -1.000000000000000e+00, + ]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(8); + let xx_ref = vec![ + 1.000000000000000e+00, + 9.238795325112867e-01, + 7.071067811865476e-01, + 3.826834323650898e-01, + 0.0, + -3.826834323650897e-01, + -7.071067811865475e-01, + -9.238795325112867e-01, + -1.000000000000000e+00, + ]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + + let xx = chebyshev_lobatto_points_standard(9); + let xx_ref = vec![ + 1.000000000000000e+00, + 9.396926207859084e-01, + 7.660444431189780e-01, + 5.000000000000001e-01, + 1.736481776669304e-01, + -1.736481776669303e-01, + -4.999999999999998e-01, + -7.660444431189779e-01, + -9.396926207859083e-01, + -1.000000000000000e+00, + ]; + vec_approx_eq(&xx, &xx_ref, 1e-15); + check_segment_symmetry(&xx); + } } From f06553f0331e961047cfba0c270e7179e227562f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 14:43:48 +1000 Subject: [PATCH 28/93] [lab] Improve InterpChebyshev --- russell_lab/examples/algo_interp_chebyshev.rs | 2 +- russell_lab/src/algo/interp_chebyshev.rs | 173 ++++++++++++------ 2 files changed, 117 insertions(+), 58 deletions(-) diff --git a/russell_lab/examples/algo_interp_chebyshev.rs b/russell_lab/examples/algo_interp_chebyshev.rs index 1df15192..40941bbd 100644 --- a/russell_lab/examples/algo_interp_chebyshev.rs +++ b/russell_lab/examples/algo_interp_chebyshev.rs @@ -9,7 +9,7 @@ fn main() -> Result<(), StrError> { // interpolant let degree = 10; let args = &mut 0; - let interp = InterpChebyshev::new(degree, xa, xb, args, f)?; + let interp = InterpChebyshev::new_with_f(degree, xa, xb, args, f)?; approx_eq(interp.eval(0.0).unwrap(), 1.0, 1e-15); // plot diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 22681833..7225c9e8 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -1,25 +1,10 @@ -use crate::math::{chebyshev_tn, PI}; +use crate::math::{chebyshev_lobatto_points_standard, chebyshev_tn, PI}; use crate::StrError; use crate::Vector; /// Defines the tolerance to make sure that the range [xa, xb] is not zero const TOL_RANGE: f64 = 1.0e-8; -// The grid points are sorted from +1 to -1; -// -// See the note below from Reference # 1 (page 86): -// -// "Note that the Chebyshev quadrature points as just defined are ordered -// from right to left. This violates our general convention that quadrature points -// are ordered from left to right (see Sect. 2.2.3). Virtually all of the classical -// literature on Chebyshev spectral methods uses this reversed order. Therefore, -// in the special case of the Chebyshev quadrature points we shall adhere to the -// ordering convention that is widely used in the literature (and implemented -// in the available software). We realize that our resolution of this dilemma -// imposes upon the reader the task of mentally reversing the ordering of the -// Chebyshev nodes whenever they are used in general formulas for orthogonal -// polynomials." (Canuto, Hussaini, Quarteroni, Zang) - /// Implements the Chebyshev interpolant and associated functions /// /// # Notes @@ -52,18 +37,18 @@ pub struct InterpChebyshev { /// Holds the difference xb - xa dx: f64, - /// Holds the expansion coefficients (Chebyshev-Gauss-Lobatto) - coef: Vec, + /// Holds the expansion coefficients (standard Chebyshev-Gauss-Lobatto) + a: Vector, - /// Holds the function evaluation at the Chebyshev-Gauss-Lobatto points - data: Vec, + /// Holds the function evaluation at the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points + uu: Vector, /// Holds the constant y=c value for a zeroth-order function constant_fx: f64, } impl InterpChebyshev { - /// Allocates a new instance + /// Allocates a new instance with given f(x) function /// /// # Input /// @@ -86,14 +71,14 @@ impl InterpChebyshev { /// // interpolant /// let degree = 10; /// let args = &mut 0; - /// let interp = InterpChebyshev::new(degree, xa, xb, args, f)?; + /// let interp = InterpChebyshev::new_with_f(degree, xa, xb, args, f)?; /// /// // check /// approx_eq(interp.eval(0.0).unwrap(), 1.0, 1e-15); /// Ok(()) /// } /// ``` - pub fn new(nn: usize, xa: f64, xb: f64, args: &mut A, f: F) -> Result + pub fn new_with_f(nn: usize, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result where F: FnMut(f64, &mut A) -> Result, { @@ -107,11 +92,52 @@ impl InterpChebyshev { xa, xb, dx: xb - xa, - coef: vec![0.0; np], - data: vec![0.0; np], + a: Vector::new(np), + uu: Vector::new(np), + constant_fx: 0.0, + }; + if nn == 0 { + interp.constant_fx = f((xa + xb) / 2.0, args)?; + } else { + chebyshev_data_vector(interp.uu.as_mut_data(), nn, xa, xb, args, &mut f)?; + chebyshev_coefficients(interp.a.as_mut_data(), interp.uu.as_mut_data(), nn); + } + Ok(interp) + } + + /// Allocates a new instance with given U vector (function evaluated at grid points) + /// + /// # Input + /// + /// * `xa` -- lower bound + /// * `xb` -- upper bound (> xa + ϵ) + /// * `uu` -- the data vector such that `Uᵢ = f(xᵢ)`; i.e., the function evaluated at the + /// **standard** (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates. These coordinates + /// are available via the [InterpChebyshev::get_points()] function. + pub fn new_with_uu(xa: f64, xb: f64, uu: &[f64]) -> Result { + if xb <= xa + TOL_RANGE { + return Err("xb must be greater than xa + ϵ"); + } + let np = uu.len(); + if np == 0 { + return Err("the number of points = uu.len() must be ≥ 1"); + } + let nn = np - 1; + let mut interp = InterpChebyshev { + nn, + np, + xa, + xb, + dx: xb - xa, + a: Vector::new(np), + uu: Vector::from(&uu), constant_fx: 0.0, }; - interp.initialize(args, f)?; + if nn == 0 { + interp.constant_fx = uu[0]; + } else { + chebyshev_coefficients(interp.a.as_mut_data(), interp.uu.as_mut_data(), nn); + } Ok(interp) } @@ -154,16 +180,20 @@ impl InterpChebyshev { if nn_max > 2048 { return Err("the maximum degree N must be ≤ 2048"); } + if xb <= xa + TOL_RANGE { + return Err("xb must be greater than xa + ϵ"); + } let np_max = nn_max + 1; - let mut work_coef = vec![0.0; np_max]; - let mut work_data = vec![0.0; np_max]; + let mut work_a = vec![0.0; np_max]; + let mut work_uu = vec![0.0; np_max]; let mut an_prev = 0.0; for nn in 1..=nn_max { - chebyshev_coefficients(&mut work_coef, &mut work_data, nn, xa, xb, args, &mut f)?; - let an = work_coef[nn]; + chebyshev_data_vector(&mut work_uu, nn, xa, xb, args, &mut f)?; + chebyshev_coefficients(&mut work_a, &work_uu, nn); + let an = work_a[nn]; if nn > 1 && f64::abs(an_prev) < tol && f64::abs(an) < tol { let nn_final = nn - 2; // -2 because the last two coefficients are zero - return Ok(InterpChebyshev::new(nn_final, xa, xb, args, f)?); + return Ok(InterpChebyshev::new_with_f(nn_final, xa, xb, args, f)?); } an_prev = an; } @@ -178,7 +208,7 @@ impl InterpChebyshev { let mut sum = 0.0; for k in 0..self.np { let y = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); - sum += self.coef[k] * chebyshev_tn(k, y); + sum += self.a[k] * chebyshev_tn(k, y); } Ok(sum) } @@ -215,24 +245,15 @@ impl InterpChebyshev { self.nn } - /// Calculates the coefficient and data vectors - fn initialize(&mut self, args: &mut A, mut f: F) -> Result<(), StrError> - where - F: FnMut(f64, &mut A) -> Result, - { - if self.nn == 0 { - self.constant_fx = f((self.xa + self.xb) / 2.0, args)?; - } else { - chebyshev_coefficients(&mut self.coef, &mut self.data, self.nn, self.xa, self.xb, args, &mut f)?; - } - Ok(()) + /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points + pub fn get_points(nn: usize) -> Vector { + chebyshev_lobatto_points_standard(nn) } } -/// Computes the Chebyshev-Gauss-Lobatto coefficients -fn chebyshev_coefficients( - work_coef: &mut [f64], - work_data: &mut [f64], +/// Computes the data vector (function evaluations at Chebyshev-Gauss-Lobatto points) +fn chebyshev_data_vector( + work_uu: &mut [f64], nn: usize, xa: f64, xb: f64, @@ -246,31 +267,41 @@ where let np = nn + 1; assert!(nn > 0); assert!(xb > xa); - assert!(work_coef.len() >= np); - assert!(work_data.len() >= np); + assert!(work_uu.len() >= np); - // data vector + // data vector U let nf = nn as f64; - let data = &mut work_data[0..np]; + let uu = &mut work_uu[0..np]; for k in 0..np { let kf = k as f64; let x = (xb + xa + (xb - xa) * f64::cos(PI * kf / nf)) / 2.0; - data[k] = (*f)(x, args)?; + uu[k] = (*f)(x, args)?; } + Ok(()) +} + +/// Computes the Chebyshev-Gauss-Lobatto coefficients +fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { + // check + let np = nn + 1; + assert!(nn > 0); + assert!(work_a.len() >= np); + assert!(work_uu.len() >= np); - // coefficients - let coef = &mut work_coef[0..np]; + // coefficients a + let nf = nn as f64; + let a = &mut work_a[0..np]; + let uu = &work_uu[0..np]; for j in 0..np { let jf = j as f64; let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; - coef[j] = 0.0; + a[j] = 0.0; for k in 0..np { let kf = k as f64; let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; - coef[j] += data[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); + a[j] += uu[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); } } - Ok(()) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -284,6 +315,34 @@ mod tests { const SAVE_FIGURE: bool = false; + #[test] + fn interp_chebyshev_new_with_f_works() { + let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); + let (xa, xb) = (-4.0, 4.0); + let nn = 2; + let args = &mut 0; + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let err = interp.estimate_max_error(100, args, f).unwrap(); + assert!(err < 1e-14); + } + #[test] + + fn interp_chebyshev_new_with_uu_works() { + let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); + let (xa, xb) = (-4.0, 4.0); + let nn = 2; + let args = &mut 0; + let yy = InterpChebyshev::get_points(nn); + let dx = xb - xa; + let uu = yy.get_mapped(|y| { + let x = (xb + xa + dx * y) / 2.0; + f(x, args).unwrap() + }); + let interp = InterpChebyshev::new_with_uu(xa, xb, uu.as_data()).unwrap(); + let err = interp.estimate_max_error(100, args, f).unwrap(); + assert!(err < 1e-14); + } + #[test] fn interp_chebyshev_new_adapt_works() { let functions = [ From a335e091a144d72d7c3a662dafac7c28ae38c7d8 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 19:24:33 +1000 Subject: [PATCH 29/93] Impl set_uu_value in InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 189 +++++++++++++++++++++-- 1 file changed, 179 insertions(+), 10 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 7225c9e8..56b63962 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -45,9 +45,87 @@ pub struct InterpChebyshev { /// Holds the constant y=c value for a zeroth-order function constant_fx: f64, + + /// Indicates that `a` and `uu` are set and ready for `eval` + ready: bool, } impl InterpChebyshev { + /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points + pub fn points(nn: usize) -> Vector { + chebyshev_lobatto_points_standard(nn) + } + + /// Allocates a new instance with uninitialized values + /// + /// **Important:** Make sure to call [InterpChebyshev::set_uu_value()] before + /// calling [InterpChebyshev::eval()] to evaluate the interpolated function. + pub fn new(nn: usize, xa: f64, xb: f64) -> Result { + if xb <= xa + TOL_RANGE { + return Err("xb must be greater than xa + ϵ"); + } + let np = nn + 1; + Ok(InterpChebyshev { + nn, + np, + xa, + xb, + dx: xb - xa, + a: Vector::new(np), + uu: Vector::new(np), + constant_fx: 0.0, + ready: false, + }) + } + + /// Sets a component of the U vector and computes the expansion coefficients + /// + /// **Note:** This function will compute the expansion coefficients when + /// the last component is set; i.e., when `i == nn` (nn is the degree N). + /// Therefore, it is recommended to call this function sequentially + /// from 0 to N (N is available via [InterpChebyshev::get_degree()]). + /// + /// # Input + /// + /// * `i` -- the index of the Chebyshev-Gauss-Lobatto point in `[0, N]` + /// with `N` being the polynomial degree. The grid points can be obtained + /// using the [InterpChebyshev::points()] function. + /// * `uui` -- the i-th component of the `U` vector; i.e., `Uᵢ` + /// + /// # Panics + /// + /// A panic will occur if `i` is out of range (it must be in `[0, N]`) + /// + /// # Examples + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// let nn = 2; + /// let (xa, xb) = (-4.0, 4.0); + /// let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + /// let yy = InterpChebyshev::points(nn); + /// let dx = xb - xa; + /// let np = nn + 1; + /// for i in 0..np { + /// let x = (xb + xa + dx * yy[i]) / 2.0; + /// interp.set_uu_value(i, x * x - 1.0); + /// } + /// approx_eq(interp.eval(0.0)?, -1.0, 1e-15); + /// Ok(()) + /// } + /// ``` + pub fn set_uu_value(&mut self, i: usize, uui: f64) { + self.uu[i] = uui; + if i == self.nn { + chebyshev_coefficients(self.a.as_mut_data(), self.uu.as_mut_data(), self.nn); + self.ready = true; + } else { + self.ready = false; + } + } + /// Allocates a new instance with given f(x) function /// /// # Input @@ -95,6 +173,7 @@ impl InterpChebyshev { a: Vector::new(np), uu: Vector::new(np), constant_fx: 0.0, + ready: true, }; if nn == 0 { interp.constant_fx = f((xa + xb) / 2.0, args)?; @@ -113,7 +192,7 @@ impl InterpChebyshev { /// * `xb` -- upper bound (> xa + ϵ) /// * `uu` -- the data vector such that `Uᵢ = f(xᵢ)`; i.e., the function evaluated at the /// **standard** (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates. These coordinates - /// are available via the [InterpChebyshev::get_points()] function. + /// are available via the [InterpChebyshev::points()] function. pub fn new_with_uu(xa: f64, xb: f64, uu: &[f64]) -> Result { if xb <= xa + TOL_RANGE { return Err("xb must be greater than xa + ϵ"); @@ -132,6 +211,7 @@ impl InterpChebyshev { a: Vector::new(np), uu: Vector::from(&uu), constant_fx: 0.0, + ready: true, }; if nn == 0 { interp.constant_fx = uu[0]; @@ -202,6 +282,9 @@ impl InterpChebyshev { /// Evaluates the interpolated f(x) function pub fn eval(&self, x: f64) -> Result { + if !self.ready { + return Err("all U components must be set first"); + } if self.nn == 0 { return Ok(self.constant_fx); } @@ -231,6 +314,9 @@ impl InterpChebyshev { where F: FnMut(f64, &mut A) -> Result, { + if !self.ready { + return Err("all U components must be set first"); + } let mut err_f = 0.0; let stations = Vector::linspace(self.xa, self.xb, nstation).unwrap(); for p in 0..nstation { @@ -244,11 +330,6 @@ impl InterpChebyshev { pub fn get_degree(&self) -> usize { self.nn } - - /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points - pub fn get_points(nn: usize) -> Vector { - chebyshev_lobatto_points_standard(nn) - } } /// Computes the data vector (function evaluations at Chebyshev-Gauss-Lobatto points) @@ -316,7 +397,42 @@ mod tests { const SAVE_FIGURE: bool = false; #[test] - fn interp_chebyshev_new_with_f_works() { + fn new_captures_errors() { + assert_eq!( + InterpChebyshev::new(2, 0.0, 0.0).err(), + Some("xb must be greater than xa + ϵ") + ); + } + + #[test] + fn new_works() { + let nn = 2; + let (xa, xb) = (-4.0, 4.0); + let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + let np = nn + 1; + assert_eq!(interp.xa, xa); + assert_eq!(interp.xb, xb); + assert_eq!(interp.dx, 8.0); + assert_eq!(interp.nn, nn); + assert_eq!(interp.np, np); + assert_eq!(interp.a.dim(), np); + assert_eq!(interp.uu.dim(), np); + assert_eq!(interp.constant_fx, 0.0); + assert_eq!(interp.ready, false); + } + + #[test] + fn new_with_f_captures_errors() { + let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); + let args = &mut 0; + assert_eq!( + InterpChebyshev::new_with_f(2, 0.0, 0.0, args, f).err(), + Some("xb must be greater than xa + ϵ") + ); + } + + #[test] + fn new_with_f_works() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let (xa, xb) = (-4.0, 4.0); let nn = 2; @@ -325,14 +441,27 @@ mod tests { let err = interp.estimate_max_error(100, args, f).unwrap(); assert!(err < 1e-14); } + #[test] + fn new_with_uu_captures_errors() { + let uu = Vector::new(0); + assert_eq!( + InterpChebyshev::new_with_uu(0.0, 0.0, uu.as_data()).err(), + Some("xb must be greater than xa + ϵ") + ); + assert_eq!( + InterpChebyshev::new_with_uu(0.0, 1.0, uu.as_data()).err(), + Some("the number of points = uu.len() must be ≥ 1") + ); + } - fn interp_chebyshev_new_with_uu_works() { + #[test] + fn new_with_uu_works() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let (xa, xb) = (-4.0, 4.0); let nn = 2; let args = &mut 0; - let yy = InterpChebyshev::get_points(nn); + let yy = InterpChebyshev::points(nn); let dx = xb - xa; let uu = yy.get_mapped(|y| { let x = (xb + xa + dx * y) / 2.0; @@ -344,7 +473,7 @@ mod tests { } #[test] - fn interp_chebyshev_new_adapt_works() { + fn new_adapt_works() { let functions = [ |_: f64, _: &mut NoArgs| Ok(2.0), |x: f64, _: &mut NoArgs| Ok(x - 0.5), @@ -410,4 +539,44 @@ mod tests { } } } + + #[test] + fn eval_captures_errors() { + let nn = 2; + let (xa, xb) = (-4.0, 4.0); + let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + assert_eq!(interp.eval(0.0).err(), Some("all U components must be set first")); + } + + #[test] + fn estimate_max_error_captures_errors() { + let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); + let args = &mut 0; + let nn = 2; + let (xa, xb) = (-4.0, 4.0); + let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + assert_eq!( + interp.estimate_max_error(100, args, f).err(), + Some("all U components must be set first") + ); + } + + #[test] + fn set_uu_value_works() { + let nn = 2; + let (xa, xb) = (-4.0, 4.0); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + let yy = InterpChebyshev::points(nn); + let dx = xb - xa; + let np = nn + 1; + for i in 0..np { + let x = (xb + xa + dx * yy[i]) / 2.0; + interp.set_uu_value(i, x * x - 1.0); + } + // check + let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); + let args = &mut 0; + let err = interp.estimate_max_error(100, args, f).unwrap(); + assert!(err < 1e-14); + } } From 2b7c251bf8f65f43c20c63ede28082ac1de160e9 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 20:12:39 +1000 Subject: [PATCH 30/93] Impl getters in InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 48 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 56b63962..7cb726df 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -22,7 +22,7 @@ const TOL_RANGE: f64 = 1.0e-8; /// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in /// Single Domains. Springer. 563p pub struct InterpChebyshev { - /// Holds the polynomial degree + /// Holds the polynomial degree N nn: usize, /// Holds the number of points (= N + 1) @@ -52,6 +52,10 @@ pub struct InterpChebyshev { impl InterpChebyshev { /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points + /// + /// # Input + /// + /// * `nn` -- polynomial degree N pub fn points(nn: usize) -> Vector { chebyshev_lobatto_points_standard(nn) } @@ -60,6 +64,12 @@ impl InterpChebyshev { /// /// **Important:** Make sure to call [InterpChebyshev::set_uu_value()] before /// calling [InterpChebyshev::eval()] to evaluate the interpolated function. + /// + /// # Input + /// + /// * `nn` -- polynomial degree N + /// * `xa` -- lower bound + /// * `xb` -- upper bound (> xa + ϵ) pub fn new(nn: usize, xa: f64, xb: f64) -> Result { if xb <= xa + TOL_RANGE { return Err("xb must be greater than xa + ϵ"); @@ -130,7 +140,7 @@ impl InterpChebyshev { /// /// # Input /// - /// * `nn` -- polynomial degree + /// * `nn` -- polynomial degree N /// * `xa` -- lower bound /// * `xb` -- upper bound (> xa + ϵ) /// * `args` -- extra arguments for the f(x) function @@ -225,7 +235,7 @@ impl InterpChebyshev { /// /// # Input /// - /// * `nn_max` -- maximum polynomial degree (≤ 2048) + /// * `nn_max` -- maximum polynomial degree N (≤ 2048) /// * `tol` -- tolerance to truncate the Chebyshev series (e.g., 1e-8) /// * `xa` -- lower bound /// * `xb` -- upper bound (> xa + ϵ) @@ -330,6 +340,23 @@ impl InterpChebyshev { pub fn get_degree(&self) -> usize { self.nn } + + /// Returns the range + /// + /// Returns `(xa, xb, dx)` with `dx = xb - xa` + pub fn get_range(&self) -> (f64, f64, f64) { + (self.xa, self.xb, self.dx) + } + + /// Returns an access to the expansion coefficients (a) + pub fn get_coefficients(&self) -> &Vector { + &self.a + } + + /// Returns the ready flag + pub fn is_ready(&self) -> bool { + self.ready + } } /// Computes the data vector (function evaluations at Chebyshev-Gauss-Lobatto points) @@ -391,7 +418,7 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { mod tests { use super::InterpChebyshev; use crate::math::PI; - use crate::{NoArgs, Vector}; + use crate::{vec_approx_eq, NoArgs, Vector}; use plotpy::{Curve, Legend, Plot}; const SAVE_FIGURE: bool = false; @@ -579,4 +606,17 @@ mod tests { let err = interp.estimate_max_error(100, args, f).unwrap(); assert!(err < 1e-14); } + + #[test] + fn getters_work() { + let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); + let (xa, xb) = (-4.0, 4.0); + let nn = 2; + let args = &mut 0; + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + assert_eq!(interp.get_degree(), 2); + assert_eq!(interp.get_range(), (-4.0, 4.0, 8.0)); + assert_eq!(interp.is_ready(), true); + vec_approx_eq(interp.get_coefficients(), &[7.0, 0.0, 8.0], 1e-15); + } } From f056970ae1626c9a93c520c94bdd6f11d82227fb Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 20:40:38 +1000 Subject: [PATCH 31/93] Clean up MultiRootSolverCheby --- .../src/algo/multi_root_solver_cheby.rs | 537 ++++++------------ 1 file changed, 166 insertions(+), 371 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 3c1d3a93..08944388 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,9 +1,5 @@ -#![allow(unused)] - -use crate::math::{chebyshev_tn, PI}; -use crate::{approx_eq, array_approx_eq, mat_eigen, mat_vec_mul, Stopwatch}; -use crate::{vec_approx_eq, StrError}; -use crate::{InterpGrid, InterpLagrange, InterpParams, RootSolverBrent}; +use crate::StrError; +use crate::{mat_eigen, InterpChebyshev, RootSolverBrent}; use crate::{Matrix, Vector}; /// Tolerance to avoid division by zero on the trailing Chebyshev coefficient @@ -16,152 +12,34 @@ const TOL_TAU: f64 = 1.0e-8; const TOL_SIGMA: f64 = 1.0e-6; pub struct MultiRootSolverCheby { - /// Degree N + /// Holds the polynomial degree N nn: usize, - /// Number of grid points (= N + 1) - np: usize, - - /// Chebyshev-Gauss-Lobatto coordinates - yy: Vector, - - /// Interpolation matrix P - pp: Matrix, - - /// Companion matrix A + /// Holds the companion matrix A aa: Matrix, - /// Function evaluations at the (standard) Chebyshev-Gauss-Lobatto grid points - u: Vector, + /// Holds the real part of the eigenvalues + l_real: Vector, - /// Coefficients of interpolation: c = P u - c: Vector, + /// Holds the imaginary part of the eigenvalues + l_imag: Vector, - /// Possible roots + /// Holds all possible roots (dim == N) roots: Vector, - - /// Indicates whether the data vector (u) has been set or not - all_data_set: bool, - - /// Lower bound - xa: f64, - - /// Upper bound - xb: f64, -} - -// Chebyshev-Gauss-Lobatto -fn chebyshev_coefficients( - workspace_uu: &mut [f64], - workspace_cc: &mut [f64], - nn: usize, - xa: f64, - xb: f64, - args: &mut A, - f: &mut F, -) where - F: FnMut(f64, &mut A) -> Result, -{ - // check - let np = nn + 1; - assert!(nn > 0); - assert!(xb > xa); - assert!(workspace_uu.len() >= np); - assert!(workspace_cc.len() >= np); - - // data vector - let nf = nn as f64; - let uu = &mut workspace_uu[0..np]; - for k in 0..np { - let kf = k as f64; - let x = (xb + xa + (xb - xa) * f64::cos(PI * kf / nf)) / 2.0; - uu[k] = (*f)(x, args).unwrap(); - } - - // coefficients - let cc = &mut workspace_cc[0..np]; - for j in 0..np { - let jf = j as f64; - let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; - cc[j] = 0.0; - for k in 0..np { - let kf = k as f64; - let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; - cc[j] += uu[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); - } - } -} - -fn chebyshev_interpolation(x: f64, xa: f64, xb: f64, cc: &Vector) -> f64 { - assert!(xb > xa); - let y = (2.0 * x - xb - xa) / (xb - xa); - let mut sum = 0.0; - for k in 0..cc.dim() { - sum += cc[k] * chebyshev_tn(k, y); - } - sum -} - -pub fn adaptive_interpolation( - nn_max: usize, - tolerance: f64, - xa: f64, - xb: f64, - args: &mut A, - mut f: F, -) -> Result -where - F: FnMut(f64, &mut A) -> Result, -{ - if nn_max > 2048 { - return Err("max N must be ≤ 2048"); - } - let np_max = nn_max + 1; - let mut workspace_uu = vec![0.0; np_max]; - let mut workspace_cc = vec![0.0; np_max]; - let nn = 1; - chebyshev_coefficients(&mut workspace_uu, &mut workspace_cc, nn, xa, xb, args, &mut f); - let mut cn_prev = workspace_cc[nn]; - println!("N = {}, cn = {}", nn, cn_prev); - for nn in 2..nn_max { - chebyshev_coefficients(&mut workspace_uu, &mut workspace_cc, nn, xa, xb, args, &mut f); - let cn = workspace_cc[nn]; - println!("N = {}, cn = {}", nn, cn); - if f64::abs(cn_prev) < tolerance && f64::abs(cn) < tolerance { - return Ok(nn - 2); - } - cn_prev = cn; - } - Err("adaptive interpolation did not converge") } impl MultiRootSolverCheby { /// Allocates a new instance + /// + /// # Input + /// + /// * `nn` -- polynomial degree N (must be ≥ 2) pub fn new(nn: usize) -> Result { // check if nn < 2 { return Err("the degree N must be ≥ 2"); } - // standard Chebyshev-Gauss-Lobatto coordinates - // let yy = standard_chebyshev_lobatto_points(nn); - let yy = Vector::new(nn + 1); - - // interpolation matrix - let nf = nn as f64; - let np = nn + 1; - let mut pp = Matrix::new(np, np); - for j in 0..np { - let jf = j as f64; - let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; - for k in 0..np { - let kf = k as f64; - let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; - pp.set(j, k, 2.0 / (qj * qk * nf) * f64::cos(PI * jf * kf / nf)); - } - } - // println!("P = \n{:.5}", pp); - // companion matrix (except last row) let mut aa = Matrix::new(nn, nn); aa.set(0, 1, 1.0); @@ -173,103 +51,65 @@ impl MultiRootSolverCheby { // done Ok(MultiRootSolverCheby { nn, - np, - yy, - pp, aa, - u: Vector::new(np), - c: Vector::new(np), + l_real: Vector::new(nn), + l_imag: Vector::new(nn), roots: Vector::new(nn), - all_data_set: false, - xa: -1.0, - xb: 1.0, }) } - /// Sets the data vector (u) from the function evaluated at the standard Chebyshev-Gauss-Lobatto points - pub fn set_data_from_function(&mut self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(), StrError> - where - F: FnMut(f64, &mut A) -> Result, - { - // check - if xb <= xa + TOL_EPS { - return Err("xb must be greater than xa + ϵ"); - } - - // set data vector - for k in 0..self.np { - let x = (xb + xa + (xb - xa) * self.yy[k]) / 2.0; - self.u[k] = f(x, args).unwrap(); - } - - // calculate the Chebyshev coefficients - mat_vec_mul(&mut self.c, 1.0, &self.pp, &self.u).unwrap(); - // println!("c = \n{:.5}", self.c); - - // done - self.all_data_set = true; - self.xa = xa; - self.xb = xb; - Ok(()) - } - - /// Computes the interpolated function - /// - /// **Warning:** The data vector (u) must be set first. - pub fn interp(&self, x: f64) -> Result { - if !self.all_data_set { - return Err("The data vector (u) must be set first"); - } - if x < self.xa || x > self.xb { - return Err("x must be in [xa, xb]"); - } - let y = (2.0 * x - self.xb - self.xa) / (self.xb - self.xa); - let mut sum = 0.0; - for k in 0..self.np { - sum += self.c[k] * chebyshev_tn(k, y); - } - Ok(sum) - } - /// Find all roots in the interval /// - /// **Warning:** The data vector (u) must be set first. - pub fn find(&mut self) -> Result<&[f64], StrError> { + /// # Input + /// + /// * `interp` -- The Chebyshev-Gauss-Lobatto interpolant with the data vector U + /// already computed. The interpolant must have the same degree N as this struct. + /// + /// # Output + /// + /// Returns a sorted list (from xa to xb) with the roots. + pub fn find(&mut self, interp: &InterpChebyshev) -> Result<&[f64], StrError> { // check - if !self.all_data_set { - return Err("The data vector (u) must be set first"); + let nn = interp.get_degree(); + if nn != self.nn { + return Err("the interpolant must have the same degree N as the solver"); + } + if !interp.is_ready() { + return Err("the interpolant must have the U vector already computed"); } - // expansion coefficients - let nn = self.nn; - let cn = self.c[nn]; - if f64::abs(cn) < TOL_EPS { - return Err("trailing Chebyshev coefficient vanishes; try another degree N"); + // last expansion coefficient + let a = interp.get_coefficients(); + let an = a[nn]; + if f64::abs(an) < TOL_EPS { + return Err("the trailing Chebyshev coefficient vanishes; try a smaller degree N"); } // last row of the companion matrix for k in 0..nn { - self.aa.set(nn - 1, k, -0.5 * self.c[k] / cn); + self.aa.set(nn - 1, k, -0.5 * a[k] / an); } self.aa.add(nn - 1, nn - 2, 0.5); - // println!("A =\n{:.4}", self.aa); // eigenvalues - let mut l_real = Vector::new(nn); - let mut l_imag = Vector::new(nn); let mut v_real = Matrix::new(nn, nn); let mut v_imag = Matrix::new(nn, nn); - mat_eigen(&mut l_real, &mut l_imag, &mut v_real, &mut v_imag, &mut self.aa).unwrap(); - - // println!("l_real =\n{}", l_real); - // println!("l_imag =\n{}", l_imag); + mat_eigen( + &mut self.l_real, + &mut self.l_imag, + &mut v_real, + &mut v_imag, + &mut self.aa, + ) + .unwrap(); // roots = real eigenvalues within the interval + let (xa, xb, dx) = interp.get_range(); let mut nroot = 0; for i in 0..nn { - if f64::abs(l_imag[i]) < TOL_TAU * f64::abs(l_real[i]) { - if f64::abs(l_real[i]) <= (1.0 + TOL_SIGMA) { - self.roots[nroot] = (self.xb + self.xa + (self.xb - self.xa) * l_real[i]) / 2.0; + if f64::abs(self.l_imag[i]) < TOL_TAU * f64::abs(self.l_real[i]) { + if f64::abs(self.l_real[i]) <= (1.0 + TOL_SIGMA) { + self.roots[nroot] = (xb + xa + dx * self.l_real[i]) / 2.0; nroot += 1; } } @@ -286,8 +126,8 @@ impl MultiRootSolverCheby { } } -/// Polishes the root using Brent's method -pub fn polish_roots( +/// Polishes the roots using Brent's method +pub fn polish_roots_brent( roots_out: &mut [f64], roots_in: &[f64], xa: f64, @@ -298,11 +138,34 @@ pub fn polish_roots( where F: FnMut(f64, &mut A) -> Result, { + // check let nr = roots_in.len(); - if nr < 2 { - return Err("this function works with at least two roots"); + if nr < 1 { + return Err("this function works with at least one root"); } + if roots_out.len() != roots_in.len() { + return Err("root_in and root_out must have the same lengths"); + } + + // handle single root let solver = RootSolverBrent::new(); + if nr == 1 { + let xr = roots_in[0]; + if xr < xa || xr > xb { + return Err("a root is outside [xa, xb]"); + } + let fa = f(xa, args)?; + let fb = f(xb, args)?; + if fa * fb < 0.0 { + let (xo, _) = solver.find(xa, xb, args, &mut f)?; + roots_out[0] = xo; + } else { + roots_out[0] = xr; + } + return Ok(()); + } + + // handle multiple roots let l = nr - 1; for i in 0..nr { let xr = roots_in[i]; @@ -325,58 +188,28 @@ where let (xo, _) = solver.find(a, b, args, &mut f)?; roots_out[i] = xo; } else { - roots_out[i] = roots_in[i]; + roots_out[i] = xr; } } Ok(()) } -/// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates -fn standard_chebyshev_lobatto_points(yy: &mut [f64], nn: usize) { - yy[0] = 1.0; - yy[nn] = -1.0; - if nn < 3 { - return; - } - let nf = nn as f64; - let d = 2.0 * nf; - let l = if (nn & 1) == 0 { - // even number of segments - nn / 2 - } else { - // odd number of segments - (nn + 3) / 2 - 1 - }; - for i in 1..l { - yy[nn - i] = -f64::sin(PI * (nf - 2.0 * (i as f64)) / d); - yy[i] = -yy[nn - i]; - } -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::adaptive_interpolation; - use super::{chebyshev_coefficients, MultiRootSolverCheby}; + use super::{polish_roots_brent, MultiRootSolverCheby}; use crate::algo::NoArgs; - use crate::array_approx_eq; - use crate::get_test_functions; - use crate::math::PI; - use crate::polish_roots; - use crate::StrError; - use crate::Vector; - use crate::{InterpGrid, InterpLagrange, InterpParams}; - use plotpy::Legend; - use plotpy::{Curve, Plot}; - - const SAVE_FIGURE: bool = true; + use crate::{array_approx_eq, get_test_functions}; + use crate::{mat_approx_eq, Matrix, StrError}; + use crate::{InterpChebyshev, Vector}; + use plotpy::{Curve, Legend, Plot}; + + const SAVE_FIGURE: bool = false; fn graph( name: &str, - xa: f64, - xb: f64, - solver: &MultiRootSolverCheby, + interp: &InterpChebyshev, roots_unpolished: &[f64], roots_polished: &[f64], args: &mut A, @@ -384,9 +217,10 @@ mod tests { ) where F: FnMut(f64, &mut A) -> Result, { + let (xa, xb, _) = interp.get_range(); let xx = Vector::linspace(xa, xb, 101).unwrap(); let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); - let yy_int = xx.get_mapped(|x| solver.interp(x).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); let mut curve_ana = Curve::new(); let mut curve_int = Curve::new(); let mut zeros_unpolished = Curve::new(); @@ -409,7 +243,7 @@ mod tests { .set_marker_line_color("#00760F") .set_line_style("None"); for root in roots_unpolished { - zeros_unpolished.draw(&[*root], &[solver.interp(*root).unwrap()]); + zeros_unpolished.draw(&[*root], &[interp.eval(*root).unwrap()]); } for root in roots_polished { zeros_polished.draw(&[*root], &[f(*root, args).unwrap()]); @@ -428,112 +262,72 @@ mod tests { .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") - .save(&format!("/tmp/russell/{}.svg", name)) + .save(&format!("/tmp/russell_lab/{}.svg", name)) .unwrap(); } #[test] - fn todo_works() { - let mut f = |x: f64, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; - let (xa, xb) = (-1.0, 1.0); - let mut workspace_uu = vec![0.0; 2000]; - let mut workspace_cc = vec![0.0; 2000]; - let args = &mut 0; - for nn in 1000..1001 { - chebyshev_coefficients(&mut workspace_uu, &mut workspace_cc, nn, xa, xb, args, &mut f); - } + fn new_captures_errors() { + let nn = 1; + assert_eq!(MultiRootSolverCheby::new(nn).err(), Some("the degree N must be ≥ 2")); } #[test] - fn adaptive_interpolation_works() { - let mut f = |x: f64, _: &mut NoArgs| -> Result { - // Ok(0.0) - // Ok(x - 0.5) - // Ok(x * x - 1.0) - // Ok(x * x * x - 0.5) - // Ok(x * x * x * x - 0.5) - // Ok(x * x * x * x * x - 0.5) - // Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)) - // Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) - Ok(f64::ln(2.0 * f64::cos(x / 2.0))) - }; - // let (xa, xb) = (-1.0, 1.0); - // let (xa, xb) = (-2.34567, 12.34567); - let (xa, xb) = (-0.995 * PI, 0.995 * PI); + fn new_works() { + let nn = 2; + let solver = MultiRootSolverCheby::new(nn).unwrap(); + let aa_correct = Matrix::from(&[[0.0, 1.0000], [0.0, 0.0]]); + mat_approx_eq(&solver.aa, &aa_correct, 1e-15); + } - let nn_max = 400; - let tol = 1e-8; - let args = &mut 0; - let nn = adaptive_interpolation(nn_max, tol, xa, xb, args, f).unwrap(); - println!("N = {}", nn); + #[test] + fn find_captures_errors() { + let (xa, xb) = (-4.0, 4.0); + let nn = 2; + let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + let nn_wrong = 3; + let mut solver = MultiRootSolverCheby::new(nn_wrong).unwrap(); + assert_eq!( + solver.find(&interp).err(), + Some("the interpolant must have the same degree N as the solver") + ); + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + assert_eq!( + solver.find(&interp).err(), + Some("the interpolant must have the U vector already computed") + ); + } - if SAVE_FIGURE { - let np = nn + 1; - let interp = InterpLagrange::new(nn, None).unwrap(); - let mut uu = Vector::new(np); - for (i, y) in interp.get_points().into_iter().enumerate() { - let x = (xb + xa + (xb - xa) * y) / 2.0; - uu[i] = f(x, args).unwrap(); - } - let xx = Vector::linspace(xa, xb, 201).unwrap(); - let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); - let yy_int = xx.get_mapped(|x| { - let y = (2.0 * x - xb - xa) / (xb - xa); - let yy = f64::max(-1.0, f64::min(1.0, y)); - interp.eval(yy, &uu).unwrap() - }); - let mut curve_ana = Curve::new(); - let mut curve_int = Curve::new(); - curve_ana.set_label("analytical"); - curve_int - .set_label("interpolated") - .set_line_style("--") - .set_marker_style(".") - .set_marker_every(5); - curve_ana.draw(xx.as_data(), yy_ana.as_data()); - curve_int.draw(xx.as_data(), yy_int.as_data()); - let mut plot = Plot::new(); - let mut legend = Legend::new(); - legend.set_num_col(4); - legend.set_outside(true); - legend.draw(); - plot.add(&curve_ana) - .add(&curve_int) - .add(&legend) - .set_cross(0.0, 0.0, "gray", "-", 1.5) - .grid_and_labels("x", "f(x)") - .save("/tmp/russell/test_adaptive_interpolation.svg") - .unwrap(); - } + #[test] + fn find_captures_trailing_zero_error() { + let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); + let (xa, xb) = (-4.0, 4.0); + let nn = 3; + let args = &mut 0; + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + assert_eq!( + solver.find(&interp).err(), + Some("the trailing Chebyshev coefficient vanishes; try a smaller degree N") + ); } #[test] - fn multi_root_solver_cheby_simple() { + fn find_works_simple() { // function - let f = |x, _: &mut NoArgs| -> Result { - // Ok(x * x - 1.0) - Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)) - // Ok(f64::ln(2.0 * f64::cos(x / 2.0))) - }; - // let (xa, xb) = (-4.0, 4.0); - let (xa, xb) = (-2.34567, 12.34567); - // let (xa, xb) = (-0.995 * PI, 0.995 * PI); - - // degree - let nn = 146; + let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); + let (xa, xb) = (-4.0, 4.0); - // solver - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - - // data + // interpolant + let nn = 2; let args = &mut 0; - solver.set_data_from_function(xa, xb, args, f).unwrap(); - // println!("U =\n{}", solver.u); + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); // find roots - let roots_unpolished = Vec::from(solver.find().unwrap()); + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); let mut roots_polished = vec![0.0; roots_unpolished.len()]; - polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, f).unwrap(); + polish_roots_brent(&mut roots_polished, &roots_unpolished, xa, xb, args, f).unwrap(); println!("n_roots = {}", roots_polished.len()); println!("roots_unpolished = {:?}", roots_unpolished); println!("roots_polished = {:?}", roots_polished); @@ -542,9 +336,7 @@ mod tests { if SAVE_FIGURE { graph( "test_multi_root_solver_cheby_simple", - xa, - xb, - &solver, + &interp, &roots_unpolished, &roots_polished, args, @@ -553,37 +345,40 @@ mod tests { } // check - // array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-12); + array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-12); } #[test] - fn multi_root_solver_cheby_works() { + fn find_works_1() { + let nn_max = 200; + let tol = 1e-8; + let args = &mut 0; let tests = get_test_functions(); - let id = 4; - let test = &tests[id]; - if test.root1.is_some() || test.root2.is_some() || test.root3.is_some() { - println!("\n==================================================================="); - println!("\n{}", test.name); - let (xa, xb) = test.range; - let nn = 20; - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let args = &mut 0; - solver.set_data_from_function(xa, xb, args, test.f).unwrap(); - let roots_unpolished = Vec::from(solver.find().unwrap()); - // let mut roots_polished = vec![0.0; roots_unpolished.len()]; - // polish_roots(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f).unwrap(); - let roots_polished = roots_unpolished.clone(); - if SAVE_FIGURE { - graph( - &format!("test_multi_root_solver_cheby_{:0>3}", id), - xa, - xb, - &solver, - &roots_unpolished, - &roots_polished, - args, - test.f, - ); + for id in &[2, 3, 4, 5, 8] { + let test = &tests[*id]; + if test.root1.is_some() || test.root2.is_some() || test.root3.is_some() { + println!("\n==================================================================="); + println!("\n{}", test.name); + let (xa, xb) = test.range; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, test.f).unwrap(); + let nn = interp.get_degree(); + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); + let mut roots_polished = vec![0.0; roots_unpolished.len()]; + polish_roots_brent(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f).unwrap(); + for xr in &roots_polished { + assert!((test.f)(*xr, args).unwrap() < 1e-10); + } + if SAVE_FIGURE { + graph( + &format!("test_multi_root_solver_cheby_{:0>3}", id), + &interp, + &roots_unpolished, + &roots_polished, + args, + test.f, + ); + } } } } From 2589db031719db25654d767348303b62c1d11c6d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 21:29:23 +1000 Subject: [PATCH 32/93] Use zz in InterpChebyshev for the natural grid coordinates --- russell_lab/src/algo/interp_chebyshev.rs | 38 ++++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 7cb726df..b9cbd233 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -7,6 +7,32 @@ const TOL_RANGE: f64 = 1.0e-8; /// Implements the Chebyshev interpolant and associated functions /// +/// The problem coordinates are `x ∈ [xa, xb]` and the grid coordinates are `z ∈ [-1, 1]` +/// Thus, consider the mapping: +/// +/// ```text +/// 2 x - xb - xa +/// z(x) = ————————————— +/// xb - xa +/// ``` +/// +/// And +/// +/// ```text +/// xb + xa + (xb - xa) z +/// x(z) = ————————————————————— +/// 2 +/// ``` +/// +/// The interpolated values are: +/// +/// ```text +/// Uⱼ = f(Xⱼ(Zⱼ)) +/// +/// where xa ≤ Xⱼ ≤ xb +/// and -1 ≤ Zⱼ ≤ 1 +/// ``` +/// /// # Notes /// /// 1. This structure is meant for interpolating data and finding (all) the roots of an equation. @@ -115,11 +141,11 @@ impl InterpChebyshev { /// let nn = 2; /// let (xa, xb) = (-4.0, 4.0); /// let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - /// let yy = InterpChebyshev::points(nn); + /// let zz = InterpChebyshev::points(nn); /// let dx = xb - xa; /// let np = nn + 1; /// for i in 0..np { - /// let x = (xb + xa + dx * yy[i]) / 2.0; + /// let x = (xb + xa + dx * zz[i]) / 2.0; /// interp.set_uu_value(i, x * x - 1.0); /// } /// approx_eq(interp.eval(0.0)?, -1.0, 1e-15); @@ -488,9 +514,9 @@ mod tests { let (xa, xb) = (-4.0, 4.0); let nn = 2; let args = &mut 0; - let yy = InterpChebyshev::points(nn); + let zz = InterpChebyshev::points(nn); let dx = xb - xa; - let uu = yy.get_mapped(|y| { + let uu = zz.get_mapped(|y| { let x = (xb + xa + dx * y) / 2.0; f(x, args).unwrap() }); @@ -593,11 +619,11 @@ mod tests { let nn = 2; let (xa, xb) = (-4.0, 4.0); let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - let yy = InterpChebyshev::points(nn); + let zz = InterpChebyshev::points(nn); let dx = xb - xa; let np = nn + 1; for i in 0..np { - let x = (xb + xa + dx * yy[i]) / 2.0; + let x = (xb + xa + dx * zz[i]) / 2.0; interp.set_uu_value(i, x * x - 1.0); } // check From e72fb1f8b0991ee8e7803f4de5c7a90c45239176 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 21:48:54 +1000 Subject: [PATCH 33/93] [Important] Remove general multi root solver using interpolation --- russell_lab/src/algo/mod.rs | 2 - .../src/algo/multi_root_solver_interp.rs | 274 ------------------ 2 files changed, 276 deletions(-) delete mode 100644 russell_lab/src/algo/multi_root_solver_interp.rs diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index b5a2de00..d8d1f1c7 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -7,7 +7,6 @@ mod linear_fitting; mod min_bracketing; mod min_solver; mod multi_root_solver_cheby; -mod multi_root_solver_interp; mod num_jacobian; mod quadrature; mod root_solver_brent; @@ -19,7 +18,6 @@ pub use crate::algo::linear_fitting::*; pub use crate::algo::min_bracketing::*; pub use crate::algo::min_solver::*; pub use crate::algo::multi_root_solver_cheby::*; -pub use crate::algo::multi_root_solver_interp::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; pub use crate::algo::root_solver_brent::*; diff --git a/russell_lab/src/algo/multi_root_solver_interp.rs b/russell_lab/src/algo/multi_root_solver_interp.rs deleted file mode 100644 index 0f1103ba..00000000 --- a/russell_lab/src/algo/multi_root_solver_interp.rs +++ /dev/null @@ -1,274 +0,0 @@ -#![allow(unused)] - -use crate::{mat_gen_eigen, Matrix, Vector}; -use crate::{vec_norm, Norm, StrError}; - -/// Tolerance to avoid division by zero and discard near-zero beta (the denominator of eigenvalue) values -const TOL_BETA: f64 = 100.0 * f64::EPSILON; - -/// Tolerance to discard root values outside the [xa, xb] range -const TOL_X_RANGE: f64 = 100.0 * f64::EPSILON; - -pub struct MultiRootSolverInterp { - /// Number of grid points (= N + 1) - npoint: usize, - - /// Weights (aka, lambda) - w: Vector, - - /// Companion matrix A - aa: Matrix, - - /// Companion matrix B - bb: Matrix, - - /// Balancing coefficients - balance: Vector, - - /// Real part of the denominator to calculate the eigenvalues - alpha_real: Vector, - - /// Imaginary part of the denominator to calculate the eigenvalues - alpha_imag: Vector, - - /// Denominators to calculate the eigenvalues - beta: Vector, - - /// The eigenvectors (as columns) - v: Matrix, - - /// Possible roots - roots: Vector, -} - -impl MultiRootSolverInterp { - /// Allocates a new instance - /// - /// # Input - /// - /// * `yy` -- holds the coordinates of the interpolant in `[-1, 1]` (e.g., Chebyshev-Gauss-Lobatto points) - /// - /// # Notes - /// - /// 1. The number of points must be ≥ 2; i.e., `yy.len() ≥ 2` - /// 2. The interpolant's degree `N` is equal to `npoint - 1` - pub fn new(yy: &Vector) -> Result { - if yy.dim() < 2 { - return Err("at least 2 grid points are required"); - } - let npoint = yy.dim(); - let nc = 1 + npoint; // companion matrix' dimension - let mut w = Vector::new(npoint); - let mut aa = Matrix::new(nc, nc); - let mut bb = Matrix::new(nc, nc); - for j in 0..npoint { - let mut prod = 1.0; - for k in 0..npoint { - if k != j { - if yy[j] == yy[k] { - return Err("grid points must not coincide"); - } - prod *= yy[j] - yy[k]; - } - } - w[j] = 1.0 / prod; - aa.set(1 + j, 1 + j, yy[j]); - bb.set(1 + j, 1 + j, 1.0); - } - Ok(MultiRootSolverInterp { - npoint, - w, - aa, - bb, - balance: Vector::new(nc), - alpha_real: Vector::new(nc), - alpha_imag: Vector::new(nc), - beta: Vector::new(nc), - v: Matrix::new(nc, nc), - roots: Vector::new(nc), - }) - } - - /// Finds multiple roots using the Lagrange interpolation method - /// - /// The problem coordinates are `x ∈ [a, b]` and the grid coordinates are `y ∈ [-1, 1]` - /// Thus, consider the mapping: - /// - /// ```text - /// 2 x - xb - xa - /// y(x) = ————————————— - /// xb - xa - /// ``` - /// - /// And - /// - /// ```text - /// xb + xa + (xb - xa) y - /// x(y) = ————————————————————— - /// 2 - /// ``` - /// - /// The interpolated values are: - /// - /// ```text - /// Uⱼ = f(Xⱼ(Yⱼ)) - /// - /// where xa ≤ Xⱼ ≤ xb - /// and -1 ≤ Yⱼ ≤ 1 - /// ``` - /// - /// # Input - /// - /// * `uu` -- holds the function evaluations at the grid points - /// * `xa` -- the lower bound (must `be < xb`) - /// * `xb` -- the upper bound (must `be > xa`) - /// - /// # Output - /// - /// Returns the roots, sorted in ascending order - pub fn find(&mut self, uu: &Vector, xa: f64, xb: f64) -> Result<&[f64], StrError> { - // check - if uu.dim() != self.npoint { - return Err("U vector must have the same dimension as the grid points vector"); - } - if xb <= xa { - return Err("xb must be greater than xa"); - } - - // balancing coefficients - let nc = 1 + self.npoint; - self.balance[0] = 1.0; - for i in 0..self.npoint { - if uu[i] == 0.0 { - self.balance[1 + i] = 1.0; - } else { - self.balance[1 + i] = f64::sqrt(f64::abs(self.w[i]) / f64::abs(uu[i])); - } - } - - // scaling coefficients - let mut row0 = Vector::new(nc); - let mut col0 = Vector::new(nc); - for j in 0..self.npoint { - let s = self.balance[1 + j]; - let t = 1.0 / s; - row0[1 + j] = -uu[j] * s; - col0[1 + j] = t * self.w[j]; - } - let sl = vec_norm(&row0, Norm::Euc); - let sr = vec_norm(&col0, Norm::Euc); - - // (balanced) companion matrix - for k in 1..nc { - self.aa.set(0, k, sl * row0[k]); - self.aa.set(k, 0, col0[k] * sr); - } - - // generalized eigenvalues - mat_gen_eigen( - &mut self.alpha_real, - &mut self.alpha_imag, - &mut self.beta, - &mut self.v, - &mut self.aa, - &mut self.bb, - )?; - - // roots = real eigenvalues - let mut nroot = 0; - for k in 0..nc { - let imaginary = f64::abs(self.alpha_imag[k]) > 0.0; - let infinite = f64::abs(self.beta[k]) < TOL_BETA; - if !imaginary && !infinite { - let y_root = self.alpha_real[k] / self.beta[k]; - let x_root = (xb + xa + (xb - xa) * y_root) / 2.0; - println!( - "alpha = ({}, {}), beta = {:.e}, lambda = {}, x_root = {}", - self.alpha_real[k], self.alpha_imag[k], self.beta[k], y_root, x_root, - ); - if x_root >= xa - TOL_X_RANGE && x_root <= xb + TOL_X_RANGE { - self.roots[nroot] = x_root; - nroot += 1; - } - } - } - for i in nroot..nc { - self.roots[i] = f64::MAX; - } - self.roots.as_mut_data().sort_by(|a, b| a.partial_cmp(b).unwrap()); - Ok(&self.roots.as_data()[..nroot]) - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#[cfg(test)] -mod tests { - use super::MultiRootSolverInterp; - use crate::algo::NoArgs; - use crate::math::chebyshev_lobatto_points; - use crate::StrError; - use crate::{array_approx_eq, Vector}; - use plotpy::{Curve, Plot}; - - const SAVE_FIGURE: bool = true; - - fn graph(name: &str, xa: f64, xb: f64, roots: &[f64], args: &mut A, mut f: F) - where - F: FnMut(f64, &mut A) -> Result, - { - let xx = Vector::linspace(xa, xb, 101).unwrap(); - let yy = xx.get_mapped(|x| f(x, args).unwrap()); - let mut curve = Curve::new(); - let mut zeros = Curve::new(); - zeros - .set_marker_style("o") - .set_marker_color("red") - .set_marker_void(true) - .set_line_style("None"); - for root in roots { - zeros.draw(&[*root], &[f(*root, args).unwrap()]); - } - curve.draw(xx.as_data(), yy.as_data()); - let mut plot = Plot::new(); - plot.add(&curve) - .add(&zeros) - .set_cross(0.0, 0.0, "gray", "-", 1.0) - .grid_and_labels("x", "f(x)") - .save(&format!("/tmp/russell/{}.svg", name)); - } - - #[test] - fn multi_root_solver_interp_works_simple() { - // function - let f = |x, _: &mut NoArgs| -> Result { Ok(x * x - 1.0) }; - let (xa, xb) = (-4.0, 4.0); - - // grid points - let nn = 44; - // for nn in 2..128 { - let yy = chebyshev_lobatto_points(nn); - - // evaluate the data over grid points - let npoint = nn + 1; - let mut uu = Vector::new(npoint); - let args = &mut 0; - for i in 0..npoint { - let x = (xb + xa + (xb - xa) * yy[i]) / 2.0; - uu[i] = f(x, args).unwrap(); - } - - // solver - let mut solver = MultiRootSolverInterp::new(&yy).unwrap(); - - // find roots - let roots = solver.find(&uu, xa, xb).unwrap(); - println!("N = {}, roots = {:?}", nn, roots); - // array_approx_eq(roots, &[-1.0, 1.0], 1e-14); - // } - - if SAVE_FIGURE { - graph("test_multi_root_solver_interp_simple", xa, xb, roots, args, f); - } - } -} From 7023cbc223d61ba4c0f1dc02cbe78b15ed73f3ce Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 22:03:14 +1000 Subject: [PATCH 34/93] Improve doc comments --- .vscode/settings.json | 2 ++ .../src/algo/multi_root_solver_cheby.rs | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 82bb405c..3b5ad0af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -111,6 +111,8 @@ "qzero", "Radau", "RINFOG", + "rootfinders", + "rootfinding", "rowcol", "rowcoliter", "rowcolrig", diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 08944388..c637e00b 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -11,6 +11,25 @@ const TOL_TAU: f64 = 1.0e-8; /// Tolerance to discard roots such that abs(Re(root)) > (1 + sigma) const TOL_SIGMA: f64 = 1.0e-6; +/// Implements a root finding solver using Chebyshev interpolation +/// +/// This struct depends on [InterpChebyshev], an interpolant using +/// Chebyshev-Gauss-Lobatto points. +/// +/// It is essential that the interpolant best approximates the +/// data/function; otherwise, not all roots can be found. +/// +/// The roots are the eigenvalues of the companion matrix. +/// +/// # References +/// +/// 1. Boyd JP (2002) Computing zeros on a real interval through Chebyshev expansion +/// and polynomial rootfinding, SIAM Journal of Numerical Analysis, 40(5):1666-1682 +/// 2. Boyd JP (2013) Finding the zeros of a univariate equation: proxy rootfinders, +/// Chebyshev interpolation, and the companion matrix, SIAM Journal of Numerical +/// Analysis, 55(2):375-396. +/// 3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy +/// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 pub struct MultiRootSolverCheby { /// Holds the polynomial degree N nn: usize, @@ -65,6 +84,9 @@ impl MultiRootSolverCheby { /// * `interp` -- The Chebyshev-Gauss-Lobatto interpolant with the data vector U /// already computed. The interpolant must have the same degree N as this struct. /// + /// **Warning:** It is essential that the interpolant best approximates the + /// data/function; otherwise, not all roots can be found. + /// /// # Output /// /// Returns a sorted list (from xa to xb) with the roots. From c288b024b0eb742556cf6b523cb93f04d25cea3a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 23:01:57 +1000 Subject: [PATCH 35/93] Impl mat_eigenvalues --- russell_lab/src/matrix/mat_eigenvalues.rs | 211 ++++++++++++++++++++++ russell_lab/src/matrix/mod.rs | 2 + 2 files changed, 213 insertions(+) create mode 100644 russell_lab/src/matrix/mat_eigenvalues.rs diff --git a/russell_lab/src/matrix/mat_eigenvalues.rs b/russell_lab/src/matrix/mat_eigenvalues.rs new file mode 100644 index 00000000..8a92bfa1 --- /dev/null +++ b/russell_lab/src/matrix/mat_eigenvalues.rs @@ -0,0 +1,211 @@ +use super::Matrix; +use crate::{to_i32, CcBool, StrError, Vector, C_FALSE}; + +extern "C" { + // Computes the eigenvalues and eigenvectors of a general matrix + // + fn c_dgeev( + calc_vl: CcBool, + calc_vr: CcBool, + n: *const i32, + a: *mut f64, + lda: *const i32, + wr: *mut f64, + wi: *mut f64, + vl: *mut f64, + ldvl: *const i32, + vr: *mut f64, + ldvr: *const i32, + work: *mut f64, + lwork: *const i32, + info: *mut i32, + ); +} + +/// (dgeev) Computes the eigenvalues of a matrix +/// +/// Computes the eigenvalues `l`, such that: +/// +/// ```text +/// a ⋅ vj = lj ⋅ vj +/// ``` +/// +/// where `lj` is the component j of `l` and `vj` is the column j of the eigenvector `v`. +/// +/// See also: +/// +/// # Output +/// +/// * `l_real` -- (m) eigenvalues; real part +/// * `l_imag` -- (m) eigenvalues; imaginary part +/// +/// # Input +/// +/// * `a` -- (m,m) general matrix (will be modified) +/// +/// # Note +/// +/// * The matrix `a` will be modified +/// +/// # Examples +/// +/// ``` +/// use russell_lab::*; +/// +/// fn main() -> Result<(), StrError> { +/// // set matrix +/// let data = [[2.0, 0.0, 0.0], [0.0, 3.0, 4.0], [0.0, 4.0, 9.0]]; +/// let mut a = Matrix::from(&data); +/// +/// // allocate output arrays +/// let m = a.nrow(); +/// let mut l_real = Vector::new(m); +/// let mut l_imag = Vector::new(m); +/// +/// // calculate the eigenvalues +/// mat_eigenvalues(&mut l_real, &mut l_imag, &mut a)?; +/// +/// // check results +/// assert_eq!( +/// format!("{:.1}", l_real), +/// "┌ ┐\n\ +/// │ 11.0 │\n\ +/// │ 1.0 │\n\ +/// │ 2.0 │\n\ +/// └ ┘" +/// ); +/// assert_eq!( +/// format!("{}", l_imag), +/// "┌ ┐\n\ +/// │ 0 │\n\ +/// │ 0 │\n\ +/// │ 0 │\n\ +/// └ ┘" +/// ); +/// Ok(()) +/// } +/// ``` +pub fn mat_eigenvalues(l_real: &mut Vector, l_imag: &mut Vector, a: &mut Matrix) -> Result<(), StrError> { + let (m, n) = a.dims(); + if m != n { + return Err("matrix must be square"); + } + if l_real.dim() != m || l_imag.dim() != m { + return Err("vectors are incompatible"); + } + let m_i32 = to_i32(m); + let lda = m_i32; + let ldu = 1; + let ldv = 1; + const EXTRA: i32 = 1; + let lwork = 4 * m_i32 + EXTRA; + let mut u = vec![0.0; ldu as usize]; + let mut v = vec![0.0; ldv as usize]; + let mut work = vec![0.0; lwork as usize]; + let mut info = 0; + unsafe { + c_dgeev( + C_FALSE, + C_FALSE, + &m_i32, + a.as_mut_data().as_mut_ptr(), + &lda, + l_real.as_mut_data().as_mut_ptr(), + l_imag.as_mut_data().as_mut_ptr(), + u.as_mut_ptr(), + &ldu, + v.as_mut_ptr(), + &ldv, + work.as_mut_ptr(), + &lwork, + &mut info, + ); + } + if info < 0 { + println!("LAPACK ERROR (dgeev): Argument #{} had an illegal value", -info); + return Err("LAPACK ERROR (dgeev): An argument had an illegal value"); + } else if info > 0 { + println!("LAPACK ERROR (dgeev): The QR algorithm failed. Elements {}+1:N of l_real and l_imag contain eigenvalues which have converged", info-1); + return Err("LAPACK ERROR (dgeev): The QR algorithm failed to compute all the eigenvalues, and no eigenvectors have been computed"); + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::mat_eigenvalues; + use crate::{vec_approx_eq, Matrix, Vector}; + + #[test] + fn mat_eigenvalues_fails_on_non_square() { + let mut a = Matrix::new(3, 4); + let m = a.nrow(); + let mut l_real = Vector::new(m); + let mut l_imag = Vector::new(m); + assert_eq!( + mat_eigenvalues(&mut l_real, &mut l_imag, &mut a), + Err("matrix must be square") + ); + } + + #[test] + fn mat_eigenvalues_fails_on_wrong_dims() { + let mut a = Matrix::new(2, 2); + let m = a.nrow(); + let mut l_real = Vector::new(m); + let mut l_imag = Vector::new(m); + let mut l_real_wrong = Vector::new(m + 1); + let mut l_imag_wrong = Vector::new(m + 1); + assert_eq!( + mat_eigenvalues(&mut l_real_wrong, &mut l_imag, &mut a), + Err("vectors are incompatible") + ); + assert_eq!( + mat_eigenvalues(&mut l_real, &mut l_imag_wrong, &mut a), + Err("vectors are incompatible") + ); + } + + #[test] + fn mat_eigenvalues_works() { + #[rustfmt::skip] + let data = [ + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + [1.0, 0.0, 0.0], + ]; + let mut a = Matrix::from(&data); + let m = a.nrow(); + let mut l_real = Vector::new(m); + let mut l_imag = Vector::new(m); + mat_eigenvalues(&mut l_real, &mut l_imag, &mut a).unwrap(); + let s3 = f64::sqrt(3.0); + let l_real_correct = &[-0.5, -0.5, 1.0]; + let l_imag_correct = &[s3 / 2.0, -s3 / 2.0, 0.0]; + vec_approx_eq(&l_real, l_real_correct, 1e-15); + vec_approx_eq(&l_imag, l_imag_correct, 1e-15); + } + + #[test] + fn mat_eigenvalues_repeated_eval_works() { + // rep: repeated eigenvalues + #[rustfmt::skip] + let data = [ + [2.0, 0.0, 0.0, 0.0], + [1.0, 2.0, 0.0, 0.0], + [0.0, 1.0, 3.0, 0.0], + [0.0, 0.0, 1.0, 3.0], + ]; + let mut a = Matrix::from(&data); + let m = a.nrow(); + let mut l_real = Vector::new(m); + let mut l_imag = Vector::new(m); + mat_eigenvalues(&mut l_real, &mut l_imag, &mut a).unwrap(); + let l_real_correct = &[3.0, 3.0, 2.0, 2.0]; + let l_imag_correct = &[0.0, 0.0, 0.0, 0.0]; + vec_approx_eq(&l_real, l_real_correct, 1e-15); + vec_approx_eq(&l_imag, l_imag_correct, 1e-15); + } +} diff --git a/russell_lab/src/matrix/mod.rs b/russell_lab/src/matrix/mod.rs index 3ae639c5..f7d79647 100644 --- a/russell_lab/src/matrix/mod.rs +++ b/russell_lab/src/matrix/mod.rs @@ -27,6 +27,7 @@ mod mat_copy; mod mat_eigen; mod mat_eigen_sym; mod mat_eigen_sym_jacobi; +mod mat_eigenvalues; mod mat_gen_eigen; mod mat_inverse; mod mat_mat_mul; @@ -68,6 +69,7 @@ pub use crate::matrix::mat_copy::*; pub use crate::matrix::mat_eigen::*; pub use crate::matrix::mat_eigen_sym::*; pub use crate::matrix::mat_eigen_sym_jacobi::*; +pub use crate::matrix::mat_eigenvalues::*; pub use crate::matrix::mat_gen_eigen::*; pub use crate::matrix::mat_inverse::*; pub use crate::matrix::mat_mat_mul::*; From 9a54d11c020607d1a7ac48326c3c34ecddf13809 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 23:06:51 +1000 Subject: [PATCH 36/93] Use mat_eigenvalues in MultiRootSolverCheby --- russell_lab/src/algo/multi_root_solver_cheby.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index c637e00b..528d56a4 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,5 +1,5 @@ use crate::StrError; -use crate::{mat_eigen, InterpChebyshev, RootSolverBrent}; +use crate::{mat_eigenvalues, InterpChebyshev, RootSolverBrent}; use crate::{Matrix, Vector}; /// Tolerance to avoid division by zero on the trailing Chebyshev coefficient @@ -114,16 +114,7 @@ impl MultiRootSolverCheby { self.aa.add(nn - 1, nn - 2, 0.5); // eigenvalues - let mut v_real = Matrix::new(nn, nn); - let mut v_imag = Matrix::new(nn, nn); - mat_eigen( - &mut self.l_real, - &mut self.l_imag, - &mut v_real, - &mut v_imag, - &mut self.aa, - ) - .unwrap(); + mat_eigenvalues(&mut self.l_real, &mut self.l_imag, &mut self.aa).unwrap(); // roots = real eigenvalues within the interval let (xa, xb, dx) = interp.get_range(); From af48a981d3667a498b8ef057b2ea887f4ee2aa87 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Mon, 24 Jun 2024 23:13:10 +1000 Subject: [PATCH 37/93] Improve doc comments --- russell_lab/src/algo/interp_chebyshev.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index b9cbd233..249c0251 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -268,6 +268,18 @@ impl InterpChebyshev { /// * `args` -- extra arguments for the f(x) function /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. /// + /// # Method + /// + /// A simple method is considered here, in which the polynomial degree N is increased + /// linearly until the two last expansion coefficients of the Chebyshev series are small + /// in absolute value. This termination strategy corresponds to the Battles and Trefethen + /// line in Table 3.3 (page 55) of Reference # 1. + /// + /// # References + /// + /// 1. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy + /// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 + /// /// # Examples /// /// ``` From 8de11597f875bda4c936d8603707c00fca8a2f4c Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 10:48:16 +1000 Subject: [PATCH 38/93] Impl Clenshaw algorithm to evaluate the Chebyshev interpolation --- .vscode/settings.json | 1 + russell_lab/src/algo/interp_chebyshev.rs | 91 +++++++++++++++++++++--- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b5ad0af..ba2d4a58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,7 @@ "Cephes", "Cheby", "Cᵢⱼₛₜ", + "Clenshaw", "cmath", "condest", "copysign", diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 249c0251..e579fbd6 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -329,6 +329,14 @@ impl InterpChebyshev { } /// Evaluates the interpolated f(x) function + /// + /// This function uses the Clenshaw algorithm (Reference # 1) to + /// avoid calling the trigonometric functions. + /// + /// # Reference + /// + /// 1. Clenshaw CW (1954) A note on the summation of Chebyshev series, + /// Mathematics of Computation, 9:118-120 pub fn eval(&self, x: f64) -> Result { if !self.ready { return Err("all U components must be set first"); @@ -336,10 +344,32 @@ impl InterpChebyshev { if self.nn == 0 { return Ok(self.constant_fx); } + let z = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); + let z2 = z * 2.0; + let mut b_k = 0.0; + let mut b_k_plus_1 = 0.0; + let mut b_k_plus_2: f64; + for k in (1..self.np).rev() { + b_k_plus_2 = b_k_plus_1; + b_k_plus_1 = b_k; + b_k = z2 * b_k_plus_1 - b_k_plus_2 + self.a[k]; + } + let res = b_k * z - b_k_plus_1 + self.a[0]; + Ok(res) + } + + /// Evaluates the interpolated f(x) function using the slower trigonometric functions + pub fn eval_using_trig(&self, x: f64) -> Result { + if !self.ready { + return Err("all U components must be set first"); + } + if self.nn == 0 { + return Ok(self.constant_fx); + } + let z = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); let mut sum = 0.0; for k in 0..self.np { - let y = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); - sum += self.a[k] * chebyshev_tn(k, y); + sum += self.a[k] * chebyshev_tn(k, z); } Ok(sum) } @@ -537,6 +567,55 @@ mod tests { assert!(err < 1e-14); } + #[test] + fn eval_captures_errors() { + let nn = 2; + let (xa, xb) = (-4.0, 4.0); + let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + assert_eq!(interp.eval(0.0).err(), Some("all U components must be set first")); + } + + #[test] + fn eval_and_eval_using_trig_work() { + let functions = [ + |_: f64, _: &mut NoArgs| Ok(2.0), + |x: f64, _: &mut NoArgs| Ok(x - 0.5), + |x: f64, _: &mut NoArgs| Ok(x * x - 1.0), + |x: f64, _: &mut NoArgs| Ok(x * x * x - 0.5), + |x: f64, _: &mut NoArgs| Ok(x * x * x * x - 0.5), + |x: f64, _: &mut NoArgs| Ok(x * x * x * x * x - 0.5), + |x: f64, _: &mut NoArgs| Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)), + |x: f64, _: &mut NoArgs| Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)), + |x: f64, _: &mut NoArgs| Ok(f64::ln(2.0 * f64::cos(x / 2.0))), + ]; + let ranges = [ + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-1.0, 1.0), + (-2.34567, 12.34567), + (-0.995 * PI, 0.995 * PI), + ]; + let err_tols = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-14, 1e-14, 1e-14]; + let nn_max = 400; + let tol = 1e-7; + let args = &mut 0; + for (index, f) in functions.into_iter().enumerate() { + let (xa, xb) = ranges[index]; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); + let stations = Vector::linspace(xa, xb, 100).unwrap(); + for x in &stations { + let res1 = interp.eval(*x).unwrap(); + let res2 = interp.eval_using_trig(*x).unwrap(); + let err = f64::abs(res1 - res2); + assert!(err <= err_tols[index]); + } + } + } + #[test] fn new_adapt_works() { let functions = [ @@ -605,14 +684,6 @@ mod tests { } } - #[test] - fn eval_captures_errors() { - let nn = 2; - let (xa, xb) = (-4.0, 4.0); - let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - assert_eq!(interp.eval(0.0).err(), Some("all U components must be set first")); - } - #[test] fn estimate_max_error_captures_errors() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); From 1e5206f7e179cac665e12456e79b20fb177c219f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 10:55:07 +1000 Subject: [PATCH 39/93] Combine test functions in InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 58 +++++++----------------- 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index e579fbd6..8dfdb3f8 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -576,7 +576,7 @@ mod tests { } #[test] - fn eval_and_eval_using_trig_work() { + fn new_adapt_and_eval_work() { let functions = [ |_: f64, _: &mut NoArgs| Ok(2.0), |x: f64, _: &mut NoArgs| Ok(x - 0.5), @@ -599,58 +599,32 @@ mod tests { (-2.34567, 12.34567), (-0.995 * PI, 0.995 * PI), ]; - let err_tols = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-14, 1e-14, 1e-14]; + let tols_adapt = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-6, 1e-6, 1e-6]; + let tols_eval = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-14, 1e-14, 1e-14]; let nn_max = 400; let tol = 1e-7; let args = &mut 0; for (index, f) in functions.into_iter().enumerate() { + // adaptive interpolation let (xa, xb) = ranges[index]; let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); - let stations = Vector::linspace(xa, xb, 100).unwrap(); - for x in &stations { + let nn = interp.get_degree(); + + // check adaptive interpolation + let err = interp.estimate_max_error(1000, args, f).unwrap(); + println!("{:0>3} : N = {:>3} : err = {:.2e}", index, nn, err); + assert!(err <= tols_adapt[index]); + + // check eval and eval_using_trig + let stations_for_eval = Vector::linspace(xa, xb, 100).unwrap(); + for x in &stations_for_eval { let res1 = interp.eval(*x).unwrap(); let res2 = interp.eval_using_trig(*x).unwrap(); let err = f64::abs(res1 - res2); - assert!(err <= err_tols[index]); + assert!(err <= tols_eval[index]); } - } - } - #[test] - fn new_adapt_works() { - let functions = [ - |_: f64, _: &mut NoArgs| Ok(2.0), - |x: f64, _: &mut NoArgs| Ok(x - 0.5), - |x: f64, _: &mut NoArgs| Ok(x * x - 1.0), - |x: f64, _: &mut NoArgs| Ok(x * x * x - 0.5), - |x: f64, _: &mut NoArgs| Ok(x * x * x * x - 0.5), - |x: f64, _: &mut NoArgs| Ok(x * x * x * x * x - 0.5), - |x: f64, _: &mut NoArgs| Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)), - |x: f64, _: &mut NoArgs| Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)), - |x: f64, _: &mut NoArgs| Ok(f64::ln(2.0 * f64::cos(x / 2.0))), - ]; - let ranges = [ - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-2.34567, 12.34567), - (-0.995 * PI, 0.995 * PI), - ]; - let err_tols = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-6, 1e-6, 1e-6]; - let nn_max = 400; - let tol = 1e-7; - let args = &mut 0; - for (index, f) in functions.into_iter().enumerate() { - let (xa, xb) = ranges[index]; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); - let nn = interp.get_degree(); - let err = interp.estimate_max_error(1000, args, f).unwrap(); - println!("{:0>3} : N = {:>3} : err = {:.2e}", index, nn, err); - assert!(err <= err_tols[index]); + // plot f(x) if SAVE_FIGURE { let xx = Vector::linspace(xa, xb, 201).unwrap(); let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); From 01c1a28988111a2a8c1a991acdcc4c3cb559c406 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 11:33:51 +1000 Subject: [PATCH 40/93] Add benchmark for Chebyshev polynomial evaluation --- russell_lab/Cargo.toml | 2 +- russell_lab/README.md | 8 + russell_lab/benches/algo_chebyshev.rs | 28 ++ .../figures/bench_chebyshev_eval_large.svg | 280 ++++++++++++++++++ .../figures/bench_chebyshev_eval_small.svg | 257 ++++++++++++++++ russell_lab/zscripts/benchmark.bash | 2 +- 6 files changed, 575 insertions(+), 2 deletions(-) create mode 100644 russell_lab/benches/algo_chebyshev.rs create mode 100644 russell_lab/data/figures/bench_chebyshev_eval_large.svg create mode 100644 russell_lab/data/figures/bench_chebyshev_eval_small.svg diff --git a/russell_lab/Cargo.toml b/russell_lab/Cargo.toml index 91da8d33..4aef70ac 100644 --- a/russell_lab/Cargo.toml +++ b/russell_lab/Cargo.toml @@ -29,5 +29,5 @@ serial_test = "3.0" cc = "1.0" [[bench]] -name = "matvec_benchmark" +name = "algo_chebyshev" harness = false diff --git a/russell_lab/README.md b/russell_lab/README.md index 31b09e8c..ecc82468 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -31,6 +31,7 @@ _This crate is part of [Russell - Rust Scientific Library](https://github.com/cp - [Read a table-formatted data file](#read-a-table-formatted-data-file) - [About the column major representation](#about-the-column-major-representation) - [Benchmarks](#benchmarks) + - [Chebyshev polynomial evaluation](#chebyshev-polynomial-evaluation) - [Jacobi Rotation versus LAPACK DSYEV](#jacobi-rotation-versus-lapack-dsyev) - [Notes for developers](#notes-for-developers) @@ -708,6 +709,13 @@ Run the benchmarks with: bash ./zscripts/benchmark.bash ``` +### Chebyshev polynomial evaluation + +Comparison of the performances of `InterpChebyshev::eval` implementing the Clenshaw algorithm and `InterpChebyshev::eval_using_trig` using the trigonometric functions. + +![Chebyshev evaluation (small)](data/figures/bench_chebyshev_eval_small.svg) + +![Chebyshev evaluation (large)](data/figures/bench_chebyshev_eval_large.svg) ### Jacobi Rotation versus LAPACK DSYEV diff --git a/russell_lab/benches/algo_chebyshev.rs b/russell_lab/benches/algo_chebyshev.rs new file mode 100644 index 00000000..9769ec7f --- /dev/null +++ b/russell_lab/benches/algo_chebyshev.rs @@ -0,0 +1,28 @@ +use criterion::BenchmarkId; +use criterion::Criterion; +use criterion::Throughput; +use criterion::{criterion_group, criterion_main}; +use russell_lab::*; + +fn bench_chebyshev_eval(c: &mut Criterion) { + let f = |x: f64, _: &mut NoArgs| Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)); + let (xa, xb) = (-1.0, 1.0); + let args = &mut 0; + let nns = [1, 5, 10, 50, 100, 150, 200, 500, 1000, 1500, 2000]; + let mut group = c.benchmark_group("chebyshev_eval"); + for nn in &nns { + group.throughput(Throughput::Elements(*nn as u64)); + group.bench_with_input(BenchmarkId::new("clenshaw", nn), nn, |b, &nn| { + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + b.iter(|| interp.eval((xa + xb) / 2.0).unwrap()); + }); + group.bench_with_input(BenchmarkId::new("trigonometric", nn), nn, |b, &nn| { + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + b.iter(|| interp.eval_using_trig((xa + xb) / 2.0).unwrap()); + }); + } + group.finish(); +} + +criterion_group!(benches, bench_chebyshev_eval); +criterion_main!(benches); diff --git a/russell_lab/data/figures/bench_chebyshev_eval_large.svg b/russell_lab/data/figures/bench_chebyshev_eval_large.svg new file mode 100644 index 00000000..c14e0077 --- /dev/null +++ b/russell_lab/data/figures/bench_chebyshev_eval_large.svg @@ -0,0 +1,280 @@ + + + +Gnuplot +Produced by GNUPLOT 5.4 patchlevel 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 2 + + + + + + + + + + + + + 4 + + + + + + + + + + + + + 6 + + + + + + + + + + + + + 8 + + + + + + + + + + + + + 10 + + + + + + + + + + + + + 12 + + + + + 0 + + + + + 200 + + + + + 400 + + + + + 600 + + + + + 800 + + + + + 1000 + + + + + 1200 + + + + + 1400 + + + + + 1600 + + + + + 1800 + + + + + 2000 + + + + + + + + + Average time (µs) + + + + + Input Size (Elements) + + + + + clenshaw + + + + + clenshaw + + + + + + gnuplot_plot_2 + + + + + + + + + + + + + + + + + trigonometric + + + + + trigonometric + + + + + + gnuplot_plot_4 + + + + + + + + + + + + + + + + + + + + + + + + + + + chebyshev_eval: Comparison + + + + + + + diff --git a/russell_lab/data/figures/bench_chebyshev_eval_small.svg b/russell_lab/data/figures/bench_chebyshev_eval_small.svg new file mode 100644 index 00000000..d61ffc47 --- /dev/null +++ b/russell_lab/data/figures/bench_chebyshev_eval_small.svg @@ -0,0 +1,257 @@ + + + +Gnuplot +Produced by GNUPLOT 5.4 patchlevel 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 10 + + + + + + + + + + + + + 20 + + + + + + + + + + + + + 30 + + + + + + + + + + + + + 40 + + + + + + + + + + + + + 50 + + + + + + + + + + + + + 60 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 10 + + + + + + + + + Average time (ns) + + + + + Input Size (Elements) + + + + + clenshaw + + + + + clenshaw + + + + + + gnuplot_plot_2 + + + + + + + + + trigonometric + + + + + trigonometric + + + + + + gnuplot_plot_4 + + + + + + + + + + + + + + + + + + + chebyshev_eval: Comparison + + + + + + + diff --git a/russell_lab/zscripts/benchmark.bash b/russell_lab/zscripts/benchmark.bash index c373c755..233ae3b0 100755 --- a/russell_lab/zscripts/benchmark.bash +++ b/russell_lab/zscripts/benchmark.bash @@ -1,3 +1,3 @@ #!/bin/bash -cargo-criterion bench \ No newline at end of file +cargo-criterion bench --features intel_mkl From 5f320dddb3da53d5193ed9ba8163d35da2d2d4b4 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 14:00:06 +1000 Subject: [PATCH 41/93] [wip] Improve algo for polishing the roots in MultiRootSolverCheby --- russell_lab/src/algo/interp_chebyshev.rs | 2 +- .../src/algo/multi_root_solver_cheby.rs | 197 +++++++++++------- 2 files changed, 118 insertions(+), 81 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 8dfdb3f8..c9eca004 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -3,7 +3,7 @@ use crate::StrError; use crate::Vector; /// Defines the tolerance to make sure that the range [xa, xb] is not zero -const TOL_RANGE: f64 = 1.0e-8; +pub(crate) const TOL_RANGE: f64 = 1.0e-5; /// Implements the Chebyshev interpolant and associated functions /// diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 528d56a4..7e336822 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -1,16 +1,7 @@ use crate::StrError; -use crate::{mat_eigenvalues, InterpChebyshev, RootSolverBrent}; +use crate::{mat_eigenvalues, InterpChebyshev, TOL_RANGE}; use crate::{Matrix, Vector}; -/// Tolerance to avoid division by zero on the trailing Chebyshev coefficient -const TOL_EPS: f64 = 1.0e-13; - -/// Tolerance to discard roots with abs(Im(root)) > tau -const TOL_TAU: f64 = 1.0e-8; - -/// Tolerance to discard roots such that abs(Re(root)) > (1 + sigma) -const TOL_SIGMA: f64 = 1.0e-6; - /// Implements a root finding solver using Chebyshev interpolation /// /// This struct depends on [InterpChebyshev], an interpolant using @@ -31,6 +22,32 @@ const TOL_SIGMA: f64 = 1.0e-6; /// 3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy /// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 pub struct MultiRootSolverCheby { + /// Holds the tolerance to avoid division by zero (e.g., on the trailing Chebyshev coefficient) + /// + /// Default = 10.0 * f64::EPSILON + pub tol_zero: f64, + + /// Holds the tolerance to discard roots with imaginary part + /// + /// Accepts only roots such that `abs(Im(root)) < tol_rel_imag * abs(Re(root))` + /// + /// Default = 1e-8 + pub tol_rel_imag: f64, + + /// Holds the tolerance to discard roots outside the boundaries + /// + /// Accepts only roots such that `abs(Re(root)) <= 1 + tol_abs_range` + /// + /// Default = [TOL_RANGE] / 10.0 + /// + /// The root will then be moved back to the lower or upper bound + pub tol_abs_boundary: f64, + + /// Holds the maximum number of iterations for the Newton polishing + /// + /// Default = 8 + pub newton_max_iterations: usize, + /// Holds the polynomial degree N nn: usize, @@ -45,6 +62,12 @@ pub struct MultiRootSolverCheby { /// Holds all possible roots (dim == N) roots: Vector, + + /// Stepsize for one-sided differences + h_osd: f64, + + /// Stepsize for central differences + h_cen: f64, } impl MultiRootSolverCheby { @@ -69,11 +92,17 @@ impl MultiRootSolverCheby { // done Ok(MultiRootSolverCheby { + tol_zero: 10.0 * f64::EPSILON, + tol_rel_imag: 1.0e-8, + tol_abs_boundary: TOL_RANGE / 10.0, + newton_max_iterations: 8, nn, aa, l_real: Vector::new(nn), l_imag: Vector::new(nn), roots: Vector::new(nn), + h_osd: f64::powf(f64::EPSILON, 1.0 / 2.0), + h_cen: f64::powf(f64::EPSILON, 1.0 / 3.0), }) } @@ -103,7 +132,7 @@ impl MultiRootSolverCheby { // last expansion coefficient let a = interp.get_coefficients(); let an = a[nn]; - if f64::abs(an) < TOL_EPS { + if f64::abs(an) < self.tol_zero { return Err("the trailing Chebyshev coefficient vanishes; try a smaller degree N"); } @@ -120,9 +149,10 @@ impl MultiRootSolverCheby { let (xa, xb, dx) = interp.get_range(); let mut nroot = 0; for i in 0..nn { - if f64::abs(self.l_imag[i]) < TOL_TAU * f64::abs(self.l_real[i]) { - if f64::abs(self.l_real[i]) <= (1.0 + TOL_SIGMA) { - self.roots[nroot] = (xb + xa + dx * self.l_real[i]) / 2.0; + if f64::abs(self.l_imag[i]) < self.tol_rel_imag * f64::abs(self.l_real[i]) { + if f64::abs(self.l_real[i]) <= 1.0 + self.tol_abs_boundary { + let x = (xb + xa + dx * self.l_real[i]) / 2.0; + self.roots[nroot] = f64::max(xa, f64::min(xb, x)); nroot += 1; } } @@ -137,81 +167,82 @@ impl MultiRootSolverCheby { // results Ok(&self.roots.as_data()[..nroot]) } -} -/// Polishes the roots using Brent's method -pub fn polish_roots_brent( - roots_out: &mut [f64], - roots_in: &[f64], - xa: f64, - xb: f64, - args: &mut A, - mut f: F, -) -> Result<(), StrError> -where - F: FnMut(f64, &mut A) -> Result, -{ - // check - let nr = roots_in.len(); - if nr < 1 { - return Err("this function works with at least one root"); - } - if roots_out.len() != roots_in.len() { - return Err("root_in and root_out must have the same lengths"); - } - - // handle single root - let solver = RootSolverBrent::new(); - if nr == 1 { - let xr = roots_in[0]; - if xr < xa || xr > xb { - return Err("a root is outside [xa, xb]"); + /// Polishes the roots using Newton's method + pub fn polish_roots_newton( + &self, + roots_out: &mut [f64], + roots_in: &[f64], + xa: f64, + xb: f64, + args: &mut A, + mut f: F, + ) -> Result<(), StrError> + where + F: FnMut(f64, &mut A) -> Result, + { + // check + let nr = roots_in.len(); + if nr < 1 { + return Err("this function works with at least one root"); } - let fa = f(xa, args)?; - let fb = f(xb, args)?; - if fa * fb < 0.0 { - let (xo, _) = solver.find(xa, xb, args, &mut f)?; - roots_out[0] = xo; - } else { - roots_out[0] = xr; + if roots_out.len() != roots_in.len() { + return Err("root_in and root_out must have the same lengths"); } - return Ok(()); - } - // handle multiple roots - let l = nr - 1; - for i in 0..nr { - let xr = roots_in[i]; - if xr < xa || xr > xb { - return Err("a root is outside [xa, xb]"); - } - let a = if i == 0 { - xa - } else { - (roots_in[i - 1] + roots_in[i]) / 2.0 - }; - let b = if i == l { - xb - } else { - (roots_in[i] + roots_in[i + 1]) / 2.0 - }; - let fa = f(a, args)?; - let fb = f(b, args)?; - if fa * fb < 0.0 { - let (xo, _) = solver.find(a, b, args, &mut f)?; - roots_out[i] = xo; - } else { - roots_out[i] = xr; + // Newton's method with approximate Jacobian + let h_cen_2 = self.h_cen * 2.0; + for r in 0..nr { + let mut x = roots_in[r]; + let mut converged = false; + for _ in 0..self.newton_max_iterations { + // check convergence on f(x) + let fx = f(x, args)?; + if f64::abs(fx) < self.tol_zero { + converged = true; + break; + } + + // calculate Jacobian + let dfdx = if x - self.h_cen <= xa { + // forward difference + (f(x + self.h_osd, args)? - f(x, args)?) / self.h_osd + } else if x + self.h_cen >= xb { + // backward difference + (f(x, args)? - f(x - self.h_osd, args)?) / self.h_osd + } else { + // central difference + (f(x + self.h_cen, args)? - f(x - self.h_cen, args)?) / h_cen_2 + }; + + // skip zero Jacobian + if f64::abs(dfdx) < self.tol_zero { + converged = true; + break; + } + + // update x + let dx = -f(x, args)? / dfdx; + if f64::abs(dx) < self.tol_zero { + converged = true; + break; + } + x += dx; + } + if !converged { + return Err("Newton's method did not converge"); + } + roots_out[r] = x; } + Ok(()) } - Ok(()) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::{polish_roots_brent, MultiRootSolverCheby}; + use super::MultiRootSolverCheby; use crate::algo::NoArgs; use crate::{array_approx_eq, get_test_functions}; use crate::{mat_approx_eq, Matrix, StrError}; @@ -340,7 +371,9 @@ mod tests { let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); let mut roots_polished = vec![0.0; roots_unpolished.len()]; - polish_roots_brent(&mut roots_polished, &roots_unpolished, xa, xb, args, f).unwrap(); + solver + .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, f) + .unwrap(); println!("n_roots = {}", roots_polished.len()); println!("roots_unpolished = {:?}", roots_unpolished); println!("roots_polished = {:?}", roots_polished); @@ -378,9 +411,13 @@ mod tests { let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); let mut roots_polished = vec![0.0; roots_unpolished.len()]; - polish_roots_brent(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f).unwrap(); + solver + .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f) + .unwrap(); for xr in &roots_polished { - assert!((test.f)(*xr, args).unwrap() < 1e-10); + let fx = (test.f)(*xr, args).unwrap(); + println!("x = {}, f(x) = {:.2e}", xr, fx); + assert!(fx < 1e-10); } if SAVE_FIGURE { graph( From f8945f34f73d8e12a3bd0968fcd14750dfa3e2a0 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 14:05:38 +1000 Subject: [PATCH 42/93] Make TOL_RANGE public --- russell_lab/src/algo/interp_chebyshev.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index c9eca004..9e847161 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -3,7 +3,7 @@ use crate::StrError; use crate::Vector; /// Defines the tolerance to make sure that the range [xa, xb] is not zero -pub(crate) const TOL_RANGE: f64 = 1.0e-5; +pub const TOL_RANGE: f64 = 1.0e-5; /// Implements the Chebyshev interpolant and associated functions /// From eef23927b88d7006150eee0618ca882733baddb2 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 14:36:26 +1000 Subject: [PATCH 43/93] Improve MultiRootSolverCheby tests --- .../test_multi_root_solver_cheby_002.svg | 1178 +++++ .../test_multi_root_solver_cheby_003.svg | 1228 ++++++ .../test_multi_root_solver_cheby_004.svg | 1289 ++++++ .../test_multi_root_solver_cheby_005.svg | 1219 ++++++ .../test_multi_root_solver_cheby_006.svg | 1256 ++++++ .../test_multi_root_solver_cheby_007.svg | 1225 ++++++ .../test_multi_root_solver_cheby_008.svg | 1216 +++++ .../test_multi_root_solver_cheby_009.svg | 3898 +++++++++++++++++ .../test_multi_root_solver_cheby_010.svg | 1282 ++++++ .../test_multi_root_solver_cheby_013.svg | 1119 +++++ .../test_multi_root_solver_cheby_simple.svg | 1282 ++++++ .../src/algo/multi_root_solver_cheby.rs | 110 +- 12 files changed, 16265 insertions(+), 37 deletions(-) create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_002.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_003.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_004.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_005.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_006.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_007.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_008.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_009.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_010.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_013.svg create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_simple.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_002.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_002.svg new file mode 100644 index 00000000..c1ebf9cc --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_002.svg @@ -0,0 +1,1178 @@ + + + + + + + + 2024-06-25T14:33:48.833080 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_003.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_003.svg new file mode 100644 index 00000000..ee252dec --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_003.svg @@ -0,0 +1,1228 @@ + + + + + + + + 2024-06-25T14:33:49.181973 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_004.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_004.svg new file mode 100644 index 00000000..e1100c05 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_004.svg @@ -0,0 +1,1289 @@ + + + + + + + + 2024-06-25T14:33:49.532778 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_005.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_005.svg new file mode 100644 index 00000000..069d2da9 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_005.svg @@ -0,0 +1,1219 @@ + + + + + + + + 2024-06-25T14:33:49.926269 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_006.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_006.svg new file mode 100644 index 00000000..c9164875 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_006.svg @@ -0,0 +1,1256 @@ + + + + + + + + 2024-06-25T14:33:50.262032 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_007.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_007.svg new file mode 100644 index 00000000..c0d831b7 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_007.svg @@ -0,0 +1,1225 @@ + + + + + + + + 2024-06-25T14:33:50.612996 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_008.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_008.svg new file mode 100644 index 00000000..6d24bb89 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_008.svg @@ -0,0 +1,1216 @@ + + + + + + + + 2024-06-25T14:33:50.965487 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_009.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_009.svg new file mode 100644 index 00000000..66d7c3e4 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_009.svg @@ -0,0 +1,3898 @@ + + + + + + + + 2024-06-25T14:33:51.392132 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_010.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_010.svg new file mode 100644 index 00000000..bbf680c0 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_010.svg @@ -0,0 +1,1282 @@ + + + + + + + + 2024-06-25T14:33:51.761831 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_013.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_013.svg new file mode 100644 index 00000000..368f5b41 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_013.svg @@ -0,0 +1,1119 @@ + + + + + + + + 2024-06-25T14:33:52.165220 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_simple.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_simple.svg new file mode 100644 index 00000000..bebdb189 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_simple.svg @@ -0,0 +1,1282 @@ + + + + + + + + 2024-06-25T13:57:42.970468 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 7e336822..407d8449 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -22,10 +22,10 @@ use crate::{Matrix, Vector}; /// 3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy /// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 pub struct MultiRootSolverCheby { - /// Holds the tolerance to avoid division by zero (e.g., on the trailing Chebyshev coefficient) + /// Holds the tolerance to avoid division by zero with the trailing Chebyshev coefficient /// - /// Default = 10.0 * f64::EPSILON - pub tol_zero: f64, + /// Default = 1e-13 + pub tol_zero_an: f64, /// Holds the tolerance to discard roots with imaginary part /// @@ -43,6 +43,16 @@ pub struct MultiRootSolverCheby { /// The root will then be moved back to the lower or upper bound pub tol_abs_boundary: f64, + /// Holds the tolerance to stop Newton's iterations when dx ~ 0 + /// + /// Default = 1e-13 + pub newton_tol_zero_dx: f64, + + /// Holds the tolerance to stop Newton's iterations when f(x) ~ 0 + /// + /// Default = 1e-13 + pub newton_tol_zero_fx: f64, + /// Holds the maximum number of iterations for the Newton polishing /// /// Default = 8 @@ -92,9 +102,11 @@ impl MultiRootSolverCheby { // done Ok(MultiRootSolverCheby { - tol_zero: 10.0 * f64::EPSILON, + tol_zero_an: 1e-13, tol_rel_imag: 1.0e-8, tol_abs_boundary: TOL_RANGE / 10.0, + newton_tol_zero_dx: 1e-13, + newton_tol_zero_fx: 1e-13, newton_max_iterations: 8, nn, aa, @@ -132,7 +144,7 @@ impl MultiRootSolverCheby { // last expansion coefficient let a = interp.get_coefficients(); let an = a[nn]; - if f64::abs(an) < self.tol_zero { + if f64::abs(an) < self.tol_zero_an { return Err("the trailing Chebyshev coefficient vanishes; try a smaller degree N"); } @@ -198,7 +210,7 @@ impl MultiRootSolverCheby { for _ in 0..self.newton_max_iterations { // check convergence on f(x) let fx = f(x, args)?; - if f64::abs(fx) < self.tol_zero { + if f64::abs(fx) < self.newton_tol_zero_fx { converged = true; break; } @@ -216,14 +228,14 @@ impl MultiRootSolverCheby { }; // skip zero Jacobian - if f64::abs(dfdx) < self.tol_zero { + if f64::abs(dfdx) < self.newton_tol_zero_fx { converged = true; break; } // update x let dx = -f(x, args)? / dfdx; - if f64::abs(dx) < self.tol_zero { + if f64::abs(dx) < self.newton_tol_zero_dx { converged = true; break; } @@ -244,7 +256,7 @@ impl MultiRootSolverCheby { mod tests { use super::MultiRootSolverCheby; use crate::algo::NoArgs; - use crate::{array_approx_eq, get_test_functions}; + use crate::{approx_eq, array_approx_eq, get_test_functions}; use crate::{mat_approx_eq, Matrix, StrError}; use crate::{InterpChebyshev, Vector}; use plotpy::{Curve, Legend, Plot}; @@ -258,11 +270,13 @@ mod tests { roots_polished: &[f64], args: &mut A, mut f: F, + nstation: usize, + fig_width: f64, ) where F: FnMut(f64, &mut A) -> Result, { let (xa, xb, _) = interp.get_range(); - let xx = Vector::linspace(xa, xb, 101).unwrap(); + let xx = Vector::linspace(xa, xb, nstation).unwrap(); let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); let mut curve_ana = Curve::new(); @@ -306,6 +320,7 @@ mod tests { .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") + .set_figure_size_points(fig_width, 500.0) .save(&format!("/tmp/russell_lab/{}.svg", name)) .unwrap(); } @@ -387,6 +402,8 @@ mod tests { &roots_polished, args, f, + 101, + 600.0, ); } @@ -395,40 +412,59 @@ mod tests { } #[test] - fn find_works_1() { + fn find_works_with_test_functions() { let nn_max = 200; let tol = 1e-8; let args = &mut 0; let tests = get_test_functions(); - for id in &[2, 3, 4, 5, 8] { + for id in &[2, 3, 4, 5, 6, 7, 8, 9, 10, 13] { let test = &tests[*id]; - if test.root1.is_some() || test.root2.is_some() || test.root3.is_some() { - println!("\n==================================================================="); - println!("\n{}", test.name); - let (xa, xb) = test.range; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, test.f).unwrap(); - let nn = interp.get_degree(); - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); - let mut roots_polished = vec![0.0; roots_unpolished.len()]; - solver - .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f) - .unwrap(); - for xr in &roots_polished { - let fx = (test.f)(*xr, args).unwrap(); - println!("x = {}, f(x) = {:.2e}", xr, fx); - assert!(fx < 1e-10); + println!("\n==================================================================="); + println!("\n{}", test.name); + let (xa, xb) = test.range; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, test.f).unwrap(); + let nn = interp.get_degree(); + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); + let mut roots_polished = vec![0.0; roots_unpolished.len()]; + solver + .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f) + .unwrap(); + for xr in &roots_polished { + let fx = (test.f)(*xr, args).unwrap(); + println!("x = {}, f(x) = {:.2e}", xr, fx); + assert!(fx < 1e-10); + if let Some(bracket) = test.root1 { + if *xr >= bracket.a && *xr <= bracket.b { + approx_eq(*xr, bracket.xo, 1e-14); + } } - if SAVE_FIGURE { - graph( - &format!("test_multi_root_solver_cheby_{:0>3}", id), - &interp, - &roots_unpolished, - &roots_polished, - args, - test.f, - ); + if let Some(bracket) = test.root2 { + if *xr >= bracket.a && *xr <= bracket.b { + approx_eq(*xr, bracket.xo, 1e-14); + } } + if let Some(bracket) = test.root3 { + if *xr >= bracket.a && *xr <= bracket.b { + approx_eq(*xr, bracket.xo, 1e-14); + } + } + } + if *id == 9 { + assert_eq!(roots_unpolished.len(), 93); + } + if SAVE_FIGURE { + let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; + graph( + &format!("test_multi_root_solver_cheby_{:0>3}", id), + &interp, + &roots_unpolished, + &roots_polished, + args, + test.f, + nstation, + fig_width, + ); } } } From 941662648402b96b7a2ae7a5ebd68a3f487b33b2 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 14:46:19 +1000 Subject: [PATCH 44/93] Add test --- .../data/figures/test_polish_roots_newton.svg | 1296 +++++++++++++++++ .../src/algo/multi_root_solver_cheby.rs | 46 +- 2 files changed, 1339 insertions(+), 3 deletions(-) create mode 100644 russell_lab/data/figures/test_polish_roots_newton.svg diff --git a/russell_lab/data/figures/test_polish_roots_newton.svg b/russell_lab/data/figures/test_polish_roots_newton.svg new file mode 100644 index 00000000..364c648c --- /dev/null +++ b/russell_lab/data/figures/test_polish_roots_newton.svg @@ -0,0 +1,1296 @@ + + + + + + + + 2024-06-25T14:45:21.971860 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 407d8449..6e97d088 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -55,7 +55,7 @@ pub struct MultiRootSolverCheby { /// Holds the maximum number of iterations for the Newton polishing /// - /// Default = 8 + /// Default = 15 pub newton_max_iterations: usize, /// Holds the polynomial degree N @@ -107,7 +107,7 @@ impl MultiRootSolverCheby { tol_abs_boundary: TOL_RANGE / 10.0, newton_tol_zero_dx: 1e-13, newton_tol_zero_fx: 1e-13, - newton_max_iterations: 8, + newton_max_iterations: 15, nn, aa, l_real: Vector::new(nn), @@ -408,7 +408,47 @@ mod tests { } // check - array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-12); + array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); + } + + #[test] + fn polish_roots_newton_works() { + // function + let f = |x, _: &mut NoArgs| Ok(x * x * x * x - 1.0); + let (xa, xb) = (-2.0, 2.0); + + // interpolant + let nn = 2; + let args = &mut 0; + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + + // find roots + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); + let mut roots_polished = vec![0.0; roots_unpolished.len()]; + solver + .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, f) + .unwrap(); + println!("n_roots = {}", roots_polished.len()); + println!("roots_unpolished = {:?}", roots_unpolished); + println!("roots_polished = {:?}", roots_polished); + + // figure + if SAVE_FIGURE { + graph( + "test_polish_roots_newton", + &interp, + &roots_unpolished, + &roots_polished, + args, + f, + 101, + 600.0, + ); + } + + // check + array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); } #[test] From ad55ad6f8b383c827524ae2e2d51fdffd9d95652 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 17:19:09 +1000 Subject: [PATCH 45/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 9e847161..163bb64d 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -524,6 +524,8 @@ mod tests { InterpChebyshev::new_with_f(2, 0.0, 0.0, args, f).err(), Some("xb must be greater than xa + ϵ") ); + let f = |_: f64, _: &mut NoArgs| Err("stop"); + assert_eq!(InterpChebyshev::new_with_f(0, 0.0, 1.0, args, f).err(), Some("stop")); } #[test] From ad99609a4a6f22746d60fc63de31f65121497d06 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 17:49:08 +1000 Subject: [PATCH 46/93] Improve test --- russell_lab/data/figures/test_new_with_uu.svg | 1126 +++++++++++++++++ russell_lab/src/algo/interp_chebyshev.rs | 55 +- 2 files changed, 1173 insertions(+), 8 deletions(-) create mode 100644 russell_lab/data/figures/test_new_with_uu.svg diff --git a/russell_lab/data/figures/test_new_with_uu.svg b/russell_lab/data/figures/test_new_with_uu.svg new file mode 100644 index 00000000..f0b821c3 --- /dev/null +++ b/russell_lab/data/figures/test_new_with_uu.svg @@ -0,0 +1,1126 @@ + + + + + + + + 2024-06-25T17:48:19.778178 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 163bb64d..32ca3596 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -486,7 +486,7 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { mod tests { use super::InterpChebyshev; use crate::math::PI; - use crate::{vec_approx_eq, NoArgs, Vector}; + use crate::{approx_eq, vec_approx_eq, NoArgs, Vector}; use plotpy::{Curve, Legend, Plot}; const SAVE_FIGURE: bool = false; @@ -554,19 +554,58 @@ mod tests { #[test] fn new_with_uu_works() { - let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); - let (xa, xb) = (-4.0, 4.0); - let nn = 2; - let args = &mut 0; + let f = |x, _: &mut NoArgs| Ok(-1.0 + f64::sqrt(1.0 + 2.0 * x * 1200.0)); + + let (xa, xb) = (0.0, 1.0); + let nn = 10; let zz = InterpChebyshev::points(nn); let dx = xb - xa; - let uu = zz.get_mapped(|y| { - let x = (xb + xa + dx * y) / 2.0; + let args = &mut 0; + let uu = zz.get_mapped(|z| { + let x = (xb + xa + dx * z) / 2.0; f(x, args).unwrap() }); let interp = InterpChebyshev::new_with_uu(xa, xb, uu.as_data()).unwrap(); + + // check + let np = nn + 1; + for i in 0..np { + let x = (xb + xa + dx * zz[i]) / 2.0; + let fxi = interp.eval(x).unwrap(); + approx_eq(fxi, interp.uu[i], 1e-13); + } let err = interp.estimate_max_error(100, args, f).unwrap(); - assert!(err < 1e-14); + println!("err = {}", err); + assert!(err < 0.73); + + // plot f(x) + if SAVE_FIGURE { + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/test_new_with_uu.svg") + .unwrap(); + } } #[test] From 42412f5c1d0bbc03107e878cbf8990f5cc99d1a4 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 17:51:36 +1000 Subject: [PATCH 47/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 32ca3596..58a35f1d 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -552,6 +552,14 @@ mod tests { ); } + #[test] + fn new_with_uu_works_n0() { + let (xa, xb) = (0.0, 1.0); + let uu = &[1.0]; + let interp = InterpChebyshev::new_with_uu(xa, xb, uu).unwrap(); + assert_eq!(interp.eval(3.0).unwrap(), 1.0); + } + #[test] fn new_with_uu_works() { let f = |x, _: &mut NoArgs| Ok(-1.0 + f64::sqrt(1.0 + 2.0 * x * 1200.0)); From 4b8e9236d8fb3755a5577b3bc2e5f57713b239cf Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 17:53:53 +1000 Subject: [PATCH 48/93] Improve test --- russell_lab/src/algo/interp_chebyshev.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 58a35f1d..5ef1096a 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -526,6 +526,7 @@ mod tests { ); let f = |_: f64, _: &mut NoArgs| Err("stop"); assert_eq!(InterpChebyshev::new_with_f(0, 0.0, 1.0, args, f).err(), Some("stop")); + assert_eq!(InterpChebyshev::new_with_f(1, 0.0, 1.0, args, f).err(), Some("stop")); } #[test] From 51fd4f5dc726c0955b94416b219e8248e8100960 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:07:03 +1000 Subject: [PATCH 49/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 42 +++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 5ef1096a..87139bb6 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -486,7 +486,7 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { mod tests { use super::InterpChebyshev; use crate::math::PI; - use crate::{approx_eq, vec_approx_eq, NoArgs, Vector}; + use crate::{approx_eq, vec_approx_eq, NoArgs, Vector, TOL_RANGE}; use plotpy::{Curve, Legend, Plot}; const SAVE_FIGURE: bool = false; @@ -625,6 +625,46 @@ mod tests { assert_eq!(interp.eval(0.0).err(), Some("all U components must be set first")); } + #[test] + fn new_adapt_captures_errors() { + struct Args { + count: usize, + } + let f = move |x: f64, a: &mut Args| { + a.count += 1; + if a.count == 3 { + return Err("stop with count = 3"); + } + if a.count == 18 { + return Err("stop with count = 18"); + } + Ok(x * x - 1.0) + }; + let mut args = Args { count: 0 }; + let (xa, xb) = (-4.0, 4.0); + let tol = 1e-3; + assert_eq!( + InterpChebyshev::new_adapt(2049, tol, xa, xb, &mut args, f).err(), + Some("the maximum degree N must be ≤ 2048") + ); + assert_eq!( + InterpChebyshev::new_adapt(2, tol, xa, xa + TOL_RANGE, &mut args, f).err(), + Some("xb must be greater than xa + ϵ") + ); + assert_eq!( + InterpChebyshev::new_adapt(1, tol, xa, xb, &mut args, f).err(), + Some("adaptive interpolation did not converge") + ); + assert_eq!( + InterpChebyshev::new_adapt(2, tol, xa, xb, &mut args, f).err(), + Some("stop with count = 3") + ); + assert_eq!( + InterpChebyshev::new_adapt(4, tol, xa, xb, &mut args, f).err(), + Some("stop with count = 18") + ); + } + #[test] fn new_adapt_and_eval_work() { let functions = [ From c1d7f55483674099144eb0e0a94e7f8eaea98413 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:07:59 +1000 Subject: [PATCH 50/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 87139bb6..36e39845 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -625,6 +625,17 @@ mod tests { assert_eq!(interp.eval(0.0).err(), Some("all U components must be set first")); } + #[test] + fn eval_using_trig_captures_errors() { + let nn = 2; + let (xa, xb) = (-4.0, 4.0); + let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + assert_eq!( + interp.eval_using_trig(0.0).err(), + Some("all U components must be set first") + ); + } + #[test] fn new_adapt_captures_errors() { struct Args { From d5a8099981c890656185fad20e2e470bae27ae76 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:21:34 +1000 Subject: [PATCH 51/93] Improve test --- russell_lab/src/algo/interp_chebyshev.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 36e39845..2258ba11 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -530,14 +530,22 @@ mod tests { } #[test] - fn new_with_f_works() { + fn new_with_f_and_estimate_max_error_work() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let (xa, xb) = (-4.0, 4.0); - let nn = 2; let args = &mut 0; + // N = 2 + let nn = 2; let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); let err = interp.estimate_max_error(100, args, f).unwrap(); + println!("N = 2, err = {:e}", err); assert!(err < 1e-14); + // N = 1 + let nn = 1; + let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let err = interp.estimate_max_error(100, args, f).unwrap(); + println!("N = 1, err = {:e}", err); + assert!(err > 15.0); } #[test] From e0e5e265405858061e72f5afd694aae3c03de31d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:30:25 +1000 Subject: [PATCH 52/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 27 ++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 2258ba11..51002d86 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -392,9 +392,6 @@ impl InterpChebyshev { where F: FnMut(f64, &mut A) -> Result, { - if !self.ready { - return Err("all U components must be set first"); - } let mut err_f = 0.0; let stations = Vector::linspace(self.xa, self.xb, nstation).unwrap(); for p in 0..nstation { @@ -768,7 +765,7 @@ mod tests { } #[test] - fn estimate_max_error_captures_errors() { + fn estimate_max_error_captures_errors_1() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; let nn = 2; @@ -780,6 +777,28 @@ mod tests { ); } + #[test] + fn estimate_max_error_captures_errors_2() { + struct Args { + count: usize, + } + let f = move |x: f64, a: &mut Args| { + a.count += 1; + if a.count == 1 { + return Err("stop with count = 1"); + } + Ok(x * x - 1.0) + }; + let mut args = Args { count: 0 }; + let (xa, xb) = (0.0, 1.0); + let uu = &[1.0]; + let interp = InterpChebyshev::new_with_uu(xa, xb, uu).unwrap(); + assert_eq!( + interp.estimate_max_error(2, &mut args, f).err(), + Some("stop with count = 1") + ); + } + #[test] fn set_uu_value_works() { let nn = 2; From 705d8a130d4cd8e19550b426967c2c2e74176d94 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:32:29 +1000 Subject: [PATCH 53/93] Disable plotting code in InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 118 +++++++++++------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 51002d86..89e95bbf 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -484,9 +484,9 @@ mod tests { use super::InterpChebyshev; use crate::math::PI; use crate::{approx_eq, vec_approx_eq, NoArgs, Vector, TOL_RANGE}; - use plotpy::{Curve, Legend, Plot}; - const SAVE_FIGURE: bool = false; + #[allow(unused)] + use plotpy::{Curve, Legend, Plot}; #[test] fn new_captures_errors() { @@ -593,33 +593,33 @@ mod tests { assert!(err < 0.73); // plot f(x) - if SAVE_FIGURE { - let xx = Vector::linspace(xa, xb, 201).unwrap(); - let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); - let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); - let mut curve_ana = Curve::new(); - let mut curve_int = Curve::new(); - curve_ana.set_label("analytical"); - curve_int - .set_label(&format!("interpolated,N={}", nn)) - .set_line_style(":") - .set_marker_style(".") - .set_marker_every(5); - curve_ana.draw(xx.as_data(), yy_ana.as_data()); - curve_int.draw(xx.as_data(), yy_int.as_data()); - let mut plot = Plot::new(); - let mut legend = Legend::new(); - legend.set_num_col(4); - legend.set_outside(true); - legend.draw(); - plot.add(&curve_ana) - .add(&curve_int) - .add(&legend) - .set_cross(0.0, 0.0, "gray", "-", 1.5) - .grid_and_labels("x", "f(x)") - .save("/tmp/russell_lab/test_new_with_uu.svg") - .unwrap(); - } + /* + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/test_new_with_uu.svg") + .unwrap(); + */ } #[test] @@ -731,36 +731,36 @@ mod tests { } // plot f(x) - if SAVE_FIGURE { - let xx = Vector::linspace(xa, xb, 201).unwrap(); - let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); - let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); - let mut curve_ana = Curve::new(); - let mut curve_int = Curve::new(); - curve_ana.set_label("analytical"); - curve_int - .set_label(&format!("interpolated,N={}", nn)) - .set_line_style(":") - .set_marker_style(".") - .set_marker_every(5); - curve_ana.draw(xx.as_data(), yy_ana.as_data()); - curve_int.draw(xx.as_data(), yy_int.as_data()); - let mut plot = Plot::new(); - let mut legend = Legend::new(); - legend.set_num_col(4); - legend.set_outside(true); - legend.draw(); - plot.add(&curve_ana) - .add(&curve_int) - .add(&legend) - .set_cross(0.0, 0.0, "gray", "-", 1.5) - .grid_and_labels("x", "f(x)") - .save(&format!( - "/tmp/russell_lab/test_interp_chebyshev_new_adapt_{:0>3}.svg", - index - )) - .unwrap(); - } + /* + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save(&format!( + "/tmp/russell_lab/test_interp_chebyshev_new_adapt_{:0>3}.svg", + index + )) + .unwrap(); + */ } } From 36ab8e55c81751d3f706e8be3a6576240ac01a26 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:36:00 +1000 Subject: [PATCH 54/93] Improve tests --- russell_lab/src/algo/interp_chebyshev.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 89e95bbf..460af9a5 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -517,6 +517,7 @@ mod tests { fn new_with_f_captures_errors() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; + _ = f(0.0, args); // for coverage tool assert_eq!( InterpChebyshev::new_with_f(2, 0.0, 0.0, args, f).err(), Some("xb must be greater than xa + ϵ") @@ -646,7 +647,7 @@ mod tests { struct Args { count: usize, } - let f = move |x: f64, a: &mut Args| { + let f = |x: f64, a: &mut Args| { a.count += 1; if a.count == 3 { return Err("stop with count = 3"); @@ -768,6 +769,7 @@ mod tests { fn estimate_max_error_captures_errors_1() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; + _ = f(0.0, args); // for coverage tool let nn = 2; let (xa, xb) = (-4.0, 4.0); let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); @@ -779,24 +781,12 @@ mod tests { #[test] fn estimate_max_error_captures_errors_2() { - struct Args { - count: usize, - } - let f = move |x: f64, a: &mut Args| { - a.count += 1; - if a.count == 1 { - return Err("stop with count = 1"); - } - Ok(x * x - 1.0) - }; - let mut args = Args { count: 0 }; + let f = |_: f64, _: &mut NoArgs| Err("stop"); + let args = &mut 0; let (xa, xb) = (0.0, 1.0); let uu = &[1.0]; let interp = InterpChebyshev::new_with_uu(xa, xb, uu).unwrap(); - assert_eq!( - interp.estimate_max_error(2, &mut args, f).err(), - Some("stop with count = 1") - ); + assert_eq!(interp.estimate_max_error(2, args, f).err(), Some("stop")); } #[test] From 4a09f117fd1f62ff083fac80f87757c649368c88 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:39:11 +1000 Subject: [PATCH 55/93] Disable plotting code in MultiRootSolverCheby --- .../src/algo/multi_root_solver_cheby.rs | 88 ++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 6e97d088..13e8434c 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -256,13 +256,17 @@ impl MultiRootSolverCheby { mod tests { use super::MultiRootSolverCheby; use crate::algo::NoArgs; + use crate::InterpChebyshev; use crate::{approx_eq, array_approx_eq, get_test_functions}; - use crate::{mat_approx_eq, Matrix, StrError}; - use crate::{InterpChebyshev, Vector}; - use plotpy::{Curve, Legend, Plot}; + use crate::{mat_approx_eq, Matrix}; + + #[allow(unused)] + use crate::{StrError, Vector}; - const SAVE_FIGURE: bool = false; + #[allow(unused)] + use plotpy::{Curve, Legend, Plot}; + /* fn graph( name: &str, interp: &InterpChebyshev, @@ -324,6 +328,7 @@ mod tests { .save(&format!("/tmp/russell_lab/{}.svg", name)) .unwrap(); } + */ #[test] fn new_captures_errors() { @@ -394,18 +399,18 @@ mod tests { println!("roots_polished = {:?}", roots_polished); // figure - if SAVE_FIGURE { - graph( - "test_multi_root_solver_cheby_simple", - &interp, - &roots_unpolished, - &roots_polished, - args, - f, - 101, - 600.0, - ); - } + /* + graph( + "test_multi_root_solver_cheby_simple", + &interp, + &roots_unpolished, + &roots_polished, + args, + f, + 101, + 600.0, + ); + */ // check array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); @@ -434,18 +439,18 @@ mod tests { println!("roots_polished = {:?}", roots_polished); // figure - if SAVE_FIGURE { - graph( - "test_polish_roots_newton", - &interp, - &roots_unpolished, - &roots_polished, - args, - f, - 101, - 600.0, - ); - } + /* + graph( + "test_polish_roots_newton", + &interp, + &roots_unpolished, + &roots_polished, + args, + f, + 101, + 600.0, + ); + */ // check array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); @@ -493,19 +498,20 @@ mod tests { if *id == 9 { assert_eq!(roots_unpolished.len(), 93); } - if SAVE_FIGURE { - let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; - graph( - &format!("test_multi_root_solver_cheby_{:0>3}", id), - &interp, - &roots_unpolished, - &roots_polished, - args, - test.f, - nstation, - fig_width, - ); - } + // figure + /* + let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; + graph( + &format!("test_multi_root_solver_cheby_{:0>3}", id), + &interp, + &roots_unpolished, + &roots_polished, + args, + test.f, + nstation, + fig_width, + ); + */ } } } From fc2d886523ff94231d3e39b9c3b05d56674ad218 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 18:44:49 +1000 Subject: [PATCH 56/93] Improve test --- .../src/algo/multi_root_solver_cheby.rs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 13e8434c..97c43e68 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -196,7 +196,7 @@ impl MultiRootSolverCheby { // check let nr = roots_in.len(); if nr < 1 { - return Err("this function works with at least one root"); + return Err("at least one root is required"); } if roots_out.len() != roots_in.len() { return Err("root_in and root_out must have the same lengths"); @@ -514,4 +514,36 @@ mod tests { */ } } + + #[test] + fn polish_roots_newton_captures_errors() { + let f = |_, _: &mut NoArgs| Ok(0.0); + let args = &mut 0; + let _ = f(0.0, args); + let (xa, xb) = (-1.0, 1.0); + let mut solver = MultiRootSolverCheby::new(2).unwrap(); + let roots_in = Vec::new(); + let mut roots_out = [0.0]; + assert_eq!( + solver + .polish_roots_newton(&mut roots_out, &roots_in, xa, xb, args, f) + .err(), + Some("at least one root is required") + ); + let roots_in = [0.0, 1.0]; + assert_eq!( + solver + .polish_roots_newton(&mut roots_out, &roots_in, xa, xb, args, f) + .err(), + Some("root_in and root_out must have the same lengths") + ); + let roots_in = [0.0]; + solver.newton_max_iterations = 0; + assert_eq!( + solver + .polish_roots_newton(&mut roots_out, &roots_in, xa, xb, args, f) + .err(), + Some("Newton's method did not converge") + ); + } } From 5f4990eae2fc97fe0c79677a8be497ec08556b99 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 19:22:55 +1000 Subject: [PATCH 57/93] Add example --- russell_lab/README.md | 61 + .../figures/algo_multi_root_solver_cheby.svg | 1419 +++++++++++++++++ .../examples/algo_multi_root_solver_cheby.rs | 92 ++ 3 files changed, 1572 insertions(+) create mode 100644 russell_lab/data/figures/algo_multi_root_solver_cheby.svg create mode 100644 russell_lab/examples/algo_multi_root_solver_cheby.rs diff --git a/russell_lab/README.md b/russell_lab/README.md index ecc82468..627b3764 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -24,6 +24,7 @@ _This crate is part of [Russell - Rust Scientific Library](https://github.com/cp - [Solution of a 1D PDE using spectral collocation](#solution-of-a-1d-pde-using-spectral-collocation) - [Numerical integration: perimeter of ellipse](#numerical-integration-perimeter-of-ellipse) - [Finding a local minimum and a root](#finding-a-local-minimum-and-a-root) + - [Finding all roots in an interval](#finding-all-roots-in-an-interval) - [Computing the pseudo-inverse matrix](#computing-the-pseudo-inverse-matrix) - [Matrix visualization](#matrix-visualization) - [Computing eigenvalues and eigenvectors](#computing-eigenvalues-and-eigenvectors) @@ -441,6 +442,66 @@ Total computation time = 907ns ``` +### Finding all roots in an interval + +This example employs a Chebyshev interpolant to find all roots of a function in an interval. The method uses adaptive interpolation followed by calculating the eigenvalues of the companion matrix. These eigenvalues equal the roots of the polynomial. After that, a simple Newton polishing algorithm is applied. + +[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_multi_root_solver_cheby.rs) + +The output looks like: + +```text +N = 184 +roots = +┌ ┐ +│ 0.04109147217011577 │ +│ 0.1530172326889439 │ +│ 0.25340124027487965 │ +│ 0.33978749525956276 │ +│ 0.47590538542276967 │ +│ 0.5162732673126048 │ +└ ┘ +f @ roots = + 1.84e-8 + -1.51e-8 + -2.40e-8 + 9.53e-9 + -1.16e-8 + -5.80e-9 + +polished roots = +┌ ┐ +│ 0.04109147155278252 │ +│ 0.15301723213859994 │ +│ 0.25340124149692184 │ +│ 0.339787495774806 │ +│ 0.47590538689192813 │ +│ 0.5162732665558162 │ +└ ┘ +f @ polished roots = + 6.66e-16 +-2.22e-16 +-2.22e-16 + 1.33e-15 + 4.44e-16 +-2.22e-16 +``` + +The function and the roots are illustrated in the figure below. + +![All roots in an interval](data/figures/algo_multi_root_solver_cheby.svg) + +**References** + +1. Boyd JP (2002) Computing zeros on a real interval through Chebyshev expansion + and polynomial rootfinding, SIAM Journal of Numerical Analysis, 40(5):1666-1682 +2. Boyd JP (2013) Finding the zeros of a univariate equation: proxy rootfinders, + Chebyshev interpolation, and the companion matrix, SIAM Journal of Numerical + Analysis, 55(2):375-396. +3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy + and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 + + ### Computing the pseudo-inverse matrix diff --git a/russell_lab/data/figures/algo_multi_root_solver_cheby.svg b/russell_lab/data/figures/algo_multi_root_solver_cheby.svg new file mode 100644 index 00000000..2bc0753e --- /dev/null +++ b/russell_lab/data/figures/algo_multi_root_solver_cheby.svg @@ -0,0 +1,1419 @@ + + + + + + + + 2024-06-25T19:16:40.255565 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs new file mode 100644 index 00000000..97eab14c --- /dev/null +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -0,0 +1,92 @@ +use plotpy::{Curve, Legend, Plot}; +use russell_lab::math::PI; +use russell_lab::*; +use std::fmt::Write; + +fn main() -> Result<(), StrError> { + // function + let f = |x: f64, _: &mut NoArgs| -> Result { + Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5) + }; + let (xa, xb) = (0.0, 1.0); + let args = &mut 0; + + // adaptive interpolation + let nn_max = 200; + let tol = 1e-8; + let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f)?; + let nn = interp.get_degree(); + println!("N = {}", nn); + + // find all roots in the interval + let mut solver = MultiRootSolverCheby::new(nn)?; + let roots = Vector::from(&solver.find(&interp)?); + let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); + println!("roots =\n{}", roots); + println!("f @ roots =\n{}", print_vec_exp(&f_at_roots)); + + // polish the roots + let mut roots_polished = Vector::new(roots.dim()); + solver.polish_roots_newton(roots_polished.as_mut_data(), roots.as_data(), xa, xb, args, f)?; + let f_at_roots_polished = roots_polished.get_mapped(|x| f(x, args).unwrap()); + println!("polished roots =\n{}", roots_polished); + println!("f @ polished roots =\n{}", print_vec_exp(&f_at_roots_polished)); + + // plot the results + let nstation = 301; + let xx = Vector::linspace(xa, xb, nstation).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x, args).unwrap()); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + let mut zeros_unpolished = Curve::new(); + let mut zeros_polished = Curve::new(); + curve_ana.set_label("analytical"); + curve_int + .set_label("interpolated") + .set_line_style("--") + .set_marker_style(".") + .set_marker_every(5); + zeros_unpolished + .set_marker_style("o") + .set_marker_void(true) + .set_marker_line_color("#00760F") + .set_line_style("None"); + zeros_polished + .set_marker_style("s") + .set_marker_size(10.0) + .set_marker_void(true) + .set_marker_line_color("#00760F") + .set_line_style("None"); + for root in &roots { + zeros_unpolished.draw(&[*root], &[interp.eval(*root).unwrap()]); + } + for root in &roots_polished { + zeros_polished.draw(&[*root], &[f(*root, args).unwrap()]); + } + curve_int.draw(xx.as_data(), yy_int.as_data()); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(2); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&zeros_unpolished) + .add(&zeros_polished) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/algo_multi_root_solver_cheby.svg") + .unwrap(); + Ok(()) +} + +fn print_vec_exp(v: &Vector) -> String { + let mut buf = String::new(); + for x in v { + writeln!(&mut buf, "{:>9.2e}", x).unwrap(); + } + buf +} From f68541f69da27fc0cbdac9702c75710bde9be2c2 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 19:26:10 +1000 Subject: [PATCH 58/93] Remove algo_interp_multiple_roots example --- .../examples/algo_interp_multiple_roots.rs | 134 ------------------ 1 file changed, 134 deletions(-) delete mode 100644 russell_lab/examples/algo_interp_multiple_roots.rs diff --git a/russell_lab/examples/algo_interp_multiple_roots.rs b/russell_lab/examples/algo_interp_multiple_roots.rs deleted file mode 100644 index ebce2259..00000000 --- a/russell_lab/examples/algo_interp_multiple_roots.rs +++ /dev/null @@ -1,134 +0,0 @@ -use plotpy::{Curve, Legend, Plot}; -use russell_lab::*; - -fn main() -> Result<(), StrError> { - // function - let f = |x| x * x - 1.0; - let (xa, xb) = (-4.0, 4.0); - - // interpolant - let degree = 2; - let npoint = degree + 1; - let mut params = InterpParams::new(); - params.no_eta_normalization = true; - let interp = InterpLagrange::new(degree, Some(params)).unwrap(); - - // compute data points - let mut uu = Vector::new(npoint); - let yy = interp.get_points(); - for (i, y) in yy.into_iter().enumerate() { - let x = (xb + xa + (xb - xa) * y) / 2.0; - uu[i] = f(x); - } - println!("U = \n{}", uu); - - // interpolation - let lambda = interp.get_lambda(); - let p_interp = |x| { - let y = (2.0 * x - xb - xa) / (xb - xa); - let mut prod = 1.0; - for i in 0..npoint { - prod *= y - yy[i]; - } - let mut sum = 0.0; - for i in 0..npoint { - let wi = lambda[i]; - sum += (wi * uu[i]) / (y - yy[i]); - } - prod * sum - }; - - // companion matrix - let np = npoint; - let na = 1 + np; - let mut aa = Matrix::new(na, na); - let mut bb = Matrix::new(na, na); - for j in 0..np { - let wj = lambda[j]; - aa.set(0, 1 + j, -uu[j]); - aa.set(1 + j, 0, wj); - aa.set(1 + j, 1 + j, yy[j]); - bb.set(1 + j, 1 + j, 1.0); - } - println!("A =\n{:.3}", aa); - println!("B =\n{:.3}", bb); - - // interpolation using the companion matrix - let mut cc = Matrix::new(na, na); - let mut cc_inv = Matrix::new(na, na); - let mut p_companion = |x, a_mat, b_mat| { - // C := y B - A - let y = (2.0 * x - xb - xa) / (xb - xa); - mat_add(&mut cc, y, b_mat, -1.0, a_mat).unwrap(); - let det = mat_inverse(&mut cc_inv, &cc).unwrap(); - det - }; - - // generalized eigenvalues - let mut alpha_real = Vector::new(na); - let mut alpha_imag = Vector::new(na); - let mut beta = Vector::new(na); - let mut v = Matrix::new(na, na); - mat_gen_eigen(&mut alpha_real, &mut alpha_imag, &mut beta, &mut v, &mut aa, &mut bb)?; - - // print the results - println!("Re(α) =\n{}", alpha_real); - println!("Im(α) =\n{}", alpha_imag); - println!("β =\n{}", beta); - // println!("v =\n{:.2}", v); - - // roots = real eigenvalues - let mut roots = Vector::new(na); - let mut nroot = 0; - for i in 0..na { - let imaginary = f64::abs(alpha_imag[i]) > f64::EPSILON; - let infinite = f64::abs(beta[i]) < 10.0 * f64::EPSILON; - if !imaginary && !infinite { - let y_root = alpha_real[i] / beta[i]; - roots[nroot] = (xb + xa + (xb - xa) * y_root) / 2.0; - nroot += 1; - } - } - println!("nroot = {}", nroot); - for i in 0..nroot { - println!("root # {} = {}", i, roots[i]); - } - - // plot - let x_original = Vector::linspace(xa, xb, 101).unwrap(); - let y_original = x_original.get_mapped(|x| f(x)); - let y_interp = x_original.get_mapped(|x| p_interp(x)); - let y_companion = x_original.get_mapped(|x| p_companion(x, &aa, &bb)); - let mut curve1 = Curve::new(); - let mut curve2 = Curve::new(); - let mut curve3 = Curve::new(); - curve1 - .set_label("original") - .set_line_color("grey") - .set_line_width(15.0) - .draw(x_original.as_data(), y_original.as_data()); - curve2 - .set_label("interpolated") - .set_line_color("yellow") - .set_line_width(7.0) - .draw(x_original.as_data(), y_interp.as_data()); - curve3 - .set_label("companion") - .set_line_color("black") - .draw(x_original.as_data(), y_companion.as_data()); - let mut plot = Plot::new(); - let path = "/tmp/russell_lab/algo_interp_multiple_roots.svg"; - let mut legend = Legend::new(); - legend.set_outside(true).set_num_col(3); - legend.draw(); - plot.set_cross(0.0, 0.0, "grey", "-", 1.5) - .add(&curve1) - .add(&curve2) - .add(&curve3) - .add(&legend) - .grid_and_labels("$x$", "$f(x)$") - .save(path) - .unwrap(); - - Ok(()) -} From 48b22c26d9acc7ad9d7eef3c621bb55006718145 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 19:27:50 +1000 Subject: [PATCH 59/93] Simplify example --- russell_lab/examples/algo_multi_root_solver_cheby.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 97eab14c..5ba1b741 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -5,9 +5,7 @@ use std::fmt::Write; fn main() -> Result<(), StrError> { // function - let f = |x: f64, _: &mut NoArgs| -> Result { - Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5) - }; + let f = |x, _: &mut NoArgs| Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5); let (xa, xb) = (0.0, 1.0); let args = &mut 0; From 4ee1499e4c363ece304a9ca67228f81be7a8ab35 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Tue, 25 Jun 2024 19:34:56 +1000 Subject: [PATCH 60/93] Add doc examples --- .../src/algo/multi_root_solver_cheby.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 97c43e68..ec7106d7 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -131,6 +131,29 @@ impl MultiRootSolverCheby { /// # Output /// /// Returns a sorted list (from xa to xb) with the roots. + /// + /// # Example + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// // function + /// let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); + /// let (xa, xb) = (-2.0, 2.0); + /// let args = &mut 0; + /// + /// // interpolant + /// let nn = 2; + /// let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f)?; + /// + /// // find all roots in the interval + /// let mut solver = MultiRootSolverCheby::new(nn)?; + /// let roots = Vector::from(&solver.find(&interp)?); + /// vec_approx_eq(&roots, &[-1.0, 1.0], 1e-15); + /// Ok(()) + /// } + /// ``` pub fn find(&mut self, interp: &InterpChebyshev) -> Result<&[f64], StrError> { // check let nn = interp.get_degree(); @@ -181,6 +204,34 @@ impl MultiRootSolverCheby { } /// Polishes the roots using Newton's method + /// + /// # Examples + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// // function + /// let f = |x, _: &mut NoArgs| Ok(x * x * x * x - 1.0); + /// let (xa, xb) = (-2.0, 2.0); + /// let args = &mut 0; + /// + /// // interpolant + /// let nn = 2; + /// let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f)?; + /// + /// // find all roots in the interval + /// let mut solver = MultiRootSolverCheby::new(nn)?; + /// let roots = Vector::from(&solver.find(&interp)?); + /// vec_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate + /// + /// // polish the roots + /// let mut roots_polished = Vector::new(roots.dim()); + /// solver.polish_roots_newton(roots_polished.as_mut_data(), roots.as_data(), xa, xb, args, f)?; + /// vec_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-15); // accurate + /// Ok(()) + /// } + ///``` pub fn polish_roots_newton( &self, roots_out: &mut [f64], From 034cd68b2bfa18a123c4e7fe3f77ea3a533c110b Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 08:07:56 +1000 Subject: [PATCH 61/93] Rename function new_adapt_f --- .../examples/algo_interp_chebyshev_adapt.rs | 2 +- .../examples/algo_multi_root_solver_cheby.rs | 2 +- russell_lab/src/algo/interp_chebyshev.rs | 27 ++++++++++++------- .../src/algo/multi_root_solver_cheby.rs | 2 +- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/russell_lab/examples/algo_interp_chebyshev_adapt.rs b/russell_lab/examples/algo_interp_chebyshev_adapt.rs index 93af8f26..e02e013a 100644 --- a/russell_lab/examples/algo_interp_chebyshev_adapt.rs +++ b/russell_lab/examples/algo_interp_chebyshev_adapt.rs @@ -10,7 +10,7 @@ fn main() -> Result<(), StrError> { let nn_max = 200; let tol = 1e-8; let args = &mut 0; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f)?; + let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f)?; println!("N = {}", interp.get_degree()); // plot diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 5ba1b741..12520af7 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), StrError> { // adaptive interpolation let nn_max = 200; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f)?; + let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f)?; let nn = interp.get_degree(); println!("N = {}", nn); diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 460af9a5..5feb959c 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -294,14 +294,21 @@ impl InterpChebyshev { /// let nn_max = 200; /// let tol = 1e-8; /// let args = &mut 0; - /// let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f)?; + /// let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f)?; /// /// // check /// assert_eq!(interp.get_degree(), 2); /// Ok(()) /// } /// ``` - pub fn new_adapt(nn_max: usize, tol: f64, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result + pub fn new_adapt_f( + nn_max: usize, + tol: f64, + xa: f64, + xb: f64, + args: &mut A, + mut f: F, + ) -> Result where F: FnMut(f64, &mut A) -> Result, { @@ -643,7 +650,7 @@ mod tests { } #[test] - fn new_adapt_captures_errors() { + fn new_adapt_f_captures_errors() { struct Args { count: usize, } @@ -661,29 +668,29 @@ mod tests { let (xa, xb) = (-4.0, 4.0); let tol = 1e-3; assert_eq!( - InterpChebyshev::new_adapt(2049, tol, xa, xb, &mut args, f).err(), + InterpChebyshev::new_adapt_f(2049, tol, xa, xb, &mut args, f).err(), Some("the maximum degree N must be ≤ 2048") ); assert_eq!( - InterpChebyshev::new_adapt(2, tol, xa, xa + TOL_RANGE, &mut args, f).err(), + InterpChebyshev::new_adapt_f(2, tol, xa, xa + TOL_RANGE, &mut args, f).err(), Some("xb must be greater than xa + ϵ") ); assert_eq!( - InterpChebyshev::new_adapt(1, tol, xa, xb, &mut args, f).err(), + InterpChebyshev::new_adapt_f(1, tol, xa, xb, &mut args, f).err(), Some("adaptive interpolation did not converge") ); assert_eq!( - InterpChebyshev::new_adapt(2, tol, xa, xb, &mut args, f).err(), + InterpChebyshev::new_adapt_f(2, tol, xa, xb, &mut args, f).err(), Some("stop with count = 3") ); assert_eq!( - InterpChebyshev::new_adapt(4, tol, xa, xb, &mut args, f).err(), + InterpChebyshev::new_adapt_f(4, tol, xa, xb, &mut args, f).err(), Some("stop with count = 18") ); } #[test] - fn new_adapt_and_eval_work() { + fn new_adapt_f_and_eval_work() { let functions = [ |_: f64, _: &mut NoArgs| Ok(2.0), |x: f64, _: &mut NoArgs| Ok(x - 0.5), @@ -714,7 +721,7 @@ mod tests { for (index, f) in functions.into_iter().enumerate() { // adaptive interpolation let (xa, xb) = ranges[index]; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, f).unwrap(); + let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f).unwrap(); let nn = interp.get_degree(); // check adaptive interpolation diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index ec7106d7..ad728688 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -518,7 +518,7 @@ mod tests { println!("\n==================================================================="); println!("\n{}", test.name); let (xa, xb) = test.range; - let interp = InterpChebyshev::new_adapt(nn_max, tol, xa, xb, args, test.f).unwrap(); + let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, test.f).unwrap(); let nn = interp.get_degree(); let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); From e62dfbfebf1ce5ef25a297203e70cb7208a3e94d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 10:00:21 +1000 Subject: [PATCH 62/93] Impl new_adapt_uu in InterpChebyshev --- .../figures/algo_interp_chebyshev_data.svg | 912 ++++ .../algo_interp_chebyshev_noisy_data.svg | 1556 ++++++ ...nterp_chebyshev_new_adapt_noisy_uu_000.svg | 1119 +++++ ...nterp_chebyshev_new_adapt_noisy_uu_001.svg | 1119 +++++ ...nterp_chebyshev_new_adapt_noisy_uu_002.svg | 1368 ++++++ ...nterp_chebyshev_new_adapt_noisy_uu_003.svg | 1279 +++++ ...nterp_chebyshev_new_adapt_noisy_uu_004.svg | 1349 ++++++ ...nterp_chebyshev_new_adapt_noisy_uu_005.svg | 1221 +++++ ...nterp_chebyshev_new_adapt_noisy_uu_006.svg | 1573 ++++++ ...nterp_chebyshev_new_adapt_noisy_uu_007.svg | 4243 +++++++++++++++++ ...nterp_chebyshev_new_adapt_noisy_uu_008.svg | 1224 +++++ ...test_interp_chebyshev_new_adapt_uu_000.svg | 1118 +++++ ...test_interp_chebyshev_new_adapt_uu_001.svg | 1124 +++++ ...test_interp_chebyshev_new_adapt_uu_002.svg | 1312 +++++ ...test_interp_chebyshev_new_adapt_uu_003.svg | 1318 +++++ ...test_interp_chebyshev_new_adapt_uu_004.svg | 1214 +++++ ...test_interp_chebyshev_new_adapt_uu_005.svg | 1258 +++++ ...test_interp_chebyshev_new_adapt_uu_006.svg | 1479 ++++++ ...test_interp_chebyshev_new_adapt_uu_007.svg | 3121 ++++++++++++ ...test_interp_chebyshev_new_adapt_uu_008.svg | 1213 +++++ .../examples/algo_interp_chebyshev_data.rs | 37 + .../algo_interp_chebyshev_noisy_data.rs | 62 + russell_lab/src/algo/interp_chebyshev.rs | 345 +- 23 files changed, 30541 insertions(+), 23 deletions(-) create mode 100644 russell_lab/data/figures/algo_interp_chebyshev_data.svg create mode 100644 russell_lab/data/figures/algo_interp_chebyshev_noisy_data.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_000.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_001.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_002.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_003.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_004.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_005.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_006.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_007.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_008.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_000.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_001.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_002.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_003.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_004.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_005.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_006.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_007.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_008.svg create mode 100644 russell_lab/examples/algo_interp_chebyshev_data.rs create mode 100644 russell_lab/examples/algo_interp_chebyshev_noisy_data.rs diff --git a/russell_lab/data/figures/algo_interp_chebyshev_data.svg b/russell_lab/data/figures/algo_interp_chebyshev_data.svg new file mode 100644 index 00000000..2f122b73 --- /dev/null +++ b/russell_lab/data/figures/algo_interp_chebyshev_data.svg @@ -0,0 +1,912 @@ + + + + + + + + 2024-06-26T09:58:06.296924 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/algo_interp_chebyshev_noisy_data.svg b/russell_lab/data/figures/algo_interp_chebyshev_noisy_data.svg new file mode 100644 index 00000000..e00e0ceb --- /dev/null +++ b/russell_lab/data/figures/algo_interp_chebyshev_noisy_data.svg @@ -0,0 +1,1556 @@ + + + + + + + + 2024-06-26T09:58:48.835784 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_000.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_000.svg new file mode 100644 index 00000000..2bc62700 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_000.svg @@ -0,0 +1,1119 @@ + + + + + + + + 2024-06-26T09:35:03.812764 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_001.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_001.svg new file mode 100644 index 00000000..38042138 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_001.svg @@ -0,0 +1,1119 @@ + + + + + + + + 2024-06-26T09:35:04.157783 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_002.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_002.svg new file mode 100644 index 00000000..a14e7b6e --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_002.svg @@ -0,0 +1,1368 @@ + + + + + + + + 2024-06-26T09:35:04.517538 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_003.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_003.svg new file mode 100644 index 00000000..27d52e5a --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_003.svg @@ -0,0 +1,1279 @@ + + + + + + + + 2024-06-26T09:35:04.881707 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_004.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_004.svg new file mode 100644 index 00000000..df3e70ab --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_004.svg @@ -0,0 +1,1349 @@ + + + + + + + + 2024-06-26T09:35:05.233803 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_005.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_005.svg new file mode 100644 index 00000000..96c93c3a --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_005.svg @@ -0,0 +1,1221 @@ + + + + + + + + 2024-06-26T09:35:05.615908 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_006.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_006.svg new file mode 100644 index 00000000..f8169ca3 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_006.svg @@ -0,0 +1,1573 @@ + + + + + + + + 2024-06-26T09:35:05.947748 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_007.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_007.svg new file mode 100644 index 00000000..63593078 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_007.svg @@ -0,0 +1,4243 @@ + + + + + + + + 2024-06-26T09:35:06.445716 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_008.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_008.svg new file mode 100644 index 00000000..ba7937fb --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_008.svg @@ -0,0 +1,1224 @@ + + + + + + + + 2024-06-26T09:35:06.811404 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_000.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_000.svg new file mode 100644 index 00000000..101390fb --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_000.svg @@ -0,0 +1,1118 @@ + + + + + + + + 2024-06-26T09:32:23.500402 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_001.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_001.svg new file mode 100644 index 00000000..a252c96a --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_001.svg @@ -0,0 +1,1124 @@ + + + + + + + + 2024-06-26T09:32:23.850442 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_002.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_002.svg new file mode 100644 index 00000000..2c63a482 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_002.svg @@ -0,0 +1,1312 @@ + + + + + + + + 2024-06-26T09:32:24.199066 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_003.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_003.svg new file mode 100644 index 00000000..05267ce8 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_003.svg @@ -0,0 +1,1318 @@ + + + + + + + + 2024-06-26T09:32:24.550833 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_004.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_004.svg new file mode 100644 index 00000000..2de6e39e --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_004.svg @@ -0,0 +1,1214 @@ + + + + + + + + 2024-06-26T09:32:24.898658 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_005.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_005.svg new file mode 100644 index 00000000..e7771bf9 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_005.svg @@ -0,0 +1,1258 @@ + + + + + + + + 2024-06-26T09:32:25.232088 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_006.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_006.svg new file mode 100644 index 00000000..75ebd5ac --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_006.svg @@ -0,0 +1,1479 @@ + + + + + + + + 2024-06-26T09:32:25.584271 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_007.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_007.svg new file mode 100644 index 00000000..eb1a4321 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_007.svg @@ -0,0 +1,3121 @@ + + + + + + + + 2024-06-26T09:32:25.964201 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_008.svg b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_008.svg new file mode 100644 index 00000000..9647a1b5 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_008.svg @@ -0,0 +1,1213 @@ + + + + + + + + 2024-06-26T09:32:26.312569 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/examples/algo_interp_chebyshev_data.rs b/russell_lab/examples/algo_interp_chebyshev_data.rs new file mode 100644 index 00000000..245d415b --- /dev/null +++ b/russell_lab/examples/algo_interp_chebyshev_data.rs @@ -0,0 +1,37 @@ +use plotpy::{Curve, Legend, Plot}; +use russell_lab::*; + +fn main() -> Result<(), StrError> { + // data + let uu = [3.0, 0.5, -4.5, -7.0]; + let (xa, xb) = (0.0, 1.0); + + // interpolant + let nn_max = 100; + let tol = 1e-8; + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, &uu)?; + let nn = interp.get_degree(); + + // plot + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_int = Curve::new(); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/algo_interp_chebyshev_data.svg") + .unwrap(); + Ok(()) +} diff --git a/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs b/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs new file mode 100644 index 00000000..c9941376 --- /dev/null +++ b/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs @@ -0,0 +1,62 @@ +use plotpy::{Curve, Legend, Plot}; +use russell_lab::*; + +fn main() -> Result<(), StrError> { + // generate data with noise + let generator = |x: f64| f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x); + let (xa, xb) = (-1.0, 1.0); + let dx = xb - xa; + let nn_fit = 30; + let np_fit = nn_fit + 1; + let zz = InterpChebyshev::points(nn_fit); + let mut xx_dat = Vector::new(np_fit); + let mut uu = Vector::new(np_fit); + let dy = 0.1; + for i in 0..np_fit { + let x = (xb + xa + dx * zz[i]) / 2.0; + let noise = if i % 2 == 0 { dy } else { -dy }; + xx_dat[i] = x; + uu[i] = generator(x) + noise; + } + + // interpolant + let nn_max = 100; + let tol = 1e-8; + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data())?; + let nn = interp.get_degree(); + + // plot + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_ana = xx.get_mapped(|x| generator(x)); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + let mut curve_dat = Curve::new(); + curve_ana.set_label("generator"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_dat + .set_label("noisy data") + .set_line_style("None") + .set_marker_style("+"); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + curve_dat.draw(xx_dat.as_data(), uu.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&curve_dat) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/algo_interp_chebyshev_noisy_data.svg") + .unwrap(); + Ok(()) +} diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 5feb959c..38da1d84 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -1,6 +1,6 @@ use crate::math::{chebyshev_lobatto_points_standard, chebyshev_tn, PI}; use crate::StrError; -use crate::Vector; +use crate::{NoArgs, Vector}; /// Defines the tolerance to make sure that the range [xa, xb] is not zero pub const TOL_RANGE: f64 = 1.0e-5; @@ -335,6 +335,44 @@ impl InterpChebyshev { Err("adaptive interpolation did not converge") } + /// Allocates a new instance using adaptive interpolation on the data vector U + /// + /// # Input + /// + /// * `nn_max` -- maximum polynomial degree N (≤ 2048) + /// * `tol` -- tolerance to truncate the Chebyshev series (e.g., 1e-8) + /// * `xa` -- lower bound + /// * `xb` -- upper bound (> xa + ϵ) + /// * `uu` -- the data vector (len > 0) + /// + /// # Method + /// + /// See [InterpChebyshev::new_adapt_f()] + /// + /// # Examples + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// // data + /// let uu = [3.0, 0.5, -4.5, -7.0]; + /// let (xa, xb) = (0.0, 1.0); + /// + /// // interpolant + /// let nn_max = 100; + /// let tol = 1e-8; + /// let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, &uu)?; + /// let nn = interp.get_degree(); + /// Ok(()) + /// } + /// ``` + pub fn new_adapt_uu(nn_max: usize, tol: f64, xa: f64, xb: f64, uu: &[f64]) -> Result { + let fit = InterpChebyshev::new_with_uu(xa, xb, uu)?; + let args = &mut 0; + InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, |x, _: &mut NoArgs| fit.eval(x)) + } + /// Evaluates the interpolated f(x) function /// /// This function uses the Clenshaw algorithm (Reference # 1) to @@ -488,7 +526,7 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { #[cfg(test)] mod tests { - use super::InterpChebyshev; + use super::{chebyshev_coefficients, InterpChebyshev}; use crate::math::PI; use crate::{approx_eq, vec_approx_eq, NoArgs, Vector, TOL_RANGE}; @@ -692,29 +730,49 @@ mod tests { #[test] fn new_adapt_f_and_eval_work() { let functions = [ - |_: f64, _: &mut NoArgs| Ok(2.0), - |x: f64, _: &mut NoArgs| Ok(x - 0.5), - |x: f64, _: &mut NoArgs| Ok(x * x - 1.0), - |x: f64, _: &mut NoArgs| Ok(x * x * x - 0.5), - |x: f64, _: &mut NoArgs| Ok(x * x * x * x - 0.5), - |x: f64, _: &mut NoArgs| Ok(x * x * x * x * x - 0.5), - |x: f64, _: &mut NoArgs| Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)), - |x: f64, _: &mut NoArgs| Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)), - |x: f64, _: &mut NoArgs| Ok(f64::ln(2.0 * f64::cos(x / 2.0))), + |_: f64, _: &mut NoArgs| Ok(2.0), // 0 + |x: f64, _: &mut NoArgs| Ok(x - 0.5), // 1 + |x: f64, _: &mut NoArgs| Ok(x * x - 1.0), // 2 + |x: f64, _: &mut NoArgs| Ok(x * x * x - 0.5), // 3 + |x: f64, _: &mut NoArgs| Ok(x * x * x * x - 0.5), // 4 + |x: f64, _: &mut NoArgs| Ok(x * x * x * x * x - 0.5), // 5 + |x: f64, _: &mut NoArgs| Ok(f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x)), // 6 + |x: f64, _: &mut NoArgs| Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)), // 7 + |x: f64, _: &mut NoArgs| Ok(f64::ln(2.0 * f64::cos(x / 2.0))), // 8 ]; let ranges = [ - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-1.0, 1.0), - (-2.34567, 12.34567), - (-0.995 * PI, 0.995 * PI), + (-1.0, 1.0), // 0 + (-1.0, 1.0), // 1 + (-1.0, 1.0), // 2 + (-1.0, 1.0), // 3 + (-1.0, 1.0), // 4 + (-1.0, 1.0), // 5 + (-1.0, 1.0), // 6 + (-2.34567, 12.34567), // 7 + (-0.995 * PI, 0.995 * PI), // 8 + ]; + let tols_adapt = [ + 0.0, // 0 + 0.0, // 1 + 1e-15, // 2 + 1e-15, // 3 + 1e-15, // 4 + 1e-15, // 5 + 1e-6, // 6 + 1e-6, // 7 + 1e-6, // 8 + ]; + let tols_eval = [ + 0.0, // 0 + 0.0, // 1 + 1e-15, // 2 + 1e-15, // 3 + 1e-15, // 4 + 1e-15, // 5 + 1e-14, // 6 + 1e-14, // 7 + 1e-14, // 8 ]; - let tols_adapt = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-6, 1e-6, 1e-6]; - let tols_eval = [0.0, 0.0, 1e-15, 1e-15, 1e-15, 1e-15, 1e-14, 1e-14, 1e-14]; let nn_max = 400; let tol = 1e-7; let args = &mut 0; @@ -764,7 +822,248 @@ mod tests { .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") .save(&format!( - "/tmp/russell_lab/test_interp_chebyshev_new_adapt_{:0>3}.svg", + "/tmp/russell_lab/test_interp_chebyshev_new_adapt_f_{:0>3}.svg", + index + )) + .unwrap(); + */ + } + } + + #[test] + fn new_adapt_uu_works() { + let data_generators = [ + |_: f64| 2.0, // 0 + |x: f64| x - 0.5, // 1 + |x: f64| x * x - 1.0, // 2 + |x: f64| x * x * x - 0.5, // 3 + |x: f64| x * x * x * x - 0.5, // 4 + |x: f64| x * x * x * x * x - 0.5, // 5 + |x: f64| f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x), // 6 + |x: f64| 0.092834 * f64::sin(77.0001 + 19.87 * x), // 7 + |x: f64| f64::ln(2.0 * f64::cos(x / 2.0)), // 8 + ]; + let degrees_fit = [ + 10, // 0 + 10, // 1 + 10, // 2 + 10, // 3 + 10, // 4 + 10, // 5 + 25, // 6 + 99, // 7 + 11, // 8 + ]; + let degrees_answer = [ + 0, // 0 (reduced) + 1, // 1 (reduced) + 2, // 2 (reduced) + 3, // 3 (reduced) + 4, // 4 (reduced) + 5, // 5 (reduced) + 25, // 6 (won't go above 25) + 99, // 7 (won't go above 99) + 10, // 8 (reduced) + ]; + let ranges = [ + (-1.0, 1.0), // 0 + (-1.0, 1.0), // 1 + (-1.0, 1.0), // 2 + (-1.0, 1.0), // 3 + (-1.0, 1.0), // 4 + (-1.0, 1.0), // 5 + (-1.0, 1.0), // 6 + (-2.34567, 12.34567), // 7 + (-0.995 * PI, 0.995 * PI), // 8 + ]; + let nn_max = 400; + let tol = 1e-7; + for (index, f) in data_generators.into_iter().enumerate() { + // generate data + let (xa, xb) = ranges[index]; + let dx = xb - xa; + let nn_fit = degrees_fit[index]; + let np_fit = nn_fit + 1; + let zz = InterpChebyshev::points(nn_fit); + let mut xx_dat = Vector::new(np_fit); + let mut uu = Vector::new(np_fit); + for i in 0..np_fit { + let x = (xb + xa + dx * zz[i]) / 2.0; + xx_dat[i] = x; + uu[i] = f(x); + } + + // adaptive interpolation + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let nn = interp.get_degree(); + + // check adapted degrees + print!("{:0>3}: N = {}", index, nn); + if nn > 0 { + let mut a = Vector::new(nn + 1); + chebyshev_coefficients(a.as_mut_data(), uu.as_data(), nn); + println!(", an = {}", a[nn]); + } else { + println!(); + } + assert_eq!(nn, degrees_answer[index]); + + // plot f(x) + /* + let (nstation, fig_width) = if index == 7 { (1201, 1200.0) } else { (201, 600.0) }; + let xx = Vector::linspace(xa, xb, nstation).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x)); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + let mut curve_dat = Curve::new(); + curve_ana.set_label("generator"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_dat.set_label("data").set_line_style("None").set_marker_style("+"); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + curve_dat.draw(xx_dat.as_data(), uu.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&curve_dat) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .set_figure_size_points(fig_width, 500.0) + .save(&format!( + "/tmp/russell_lab/test_interp_chebyshev_new_adapt_uu_{:0>3}.svg", + index + )) + .unwrap(); + */ + } + } + + #[test] + fn new_adapt_noisy_uu_works() { + let data_generators = [ + |_: f64| 2.0, // 0 + |x: f64| x - 0.5, // 1 + |x: f64| x * x - 1.0, // 2 + |x: f64| x * x * x - 0.5, // 3 + |x: f64| x * x * x * x - 0.5, // 4 + |x: f64| x * x * x * x * x - 0.5, // 5 + |x: f64| f64::cos(16.0 * (x + 0.2)) * (1.0 + x) * f64::exp(x * x) / (1.0 + 9.0 * x * x), // 6 + |x: f64| 0.092834 * f64::sin(77.0001 + 19.87 * x), // 7 + |x: f64| f64::ln(2.0 * f64::cos(x / 2.0)), // 8 + ]; + let degrees_fit = [ + 10, // 0 + 10, // 1 + 10, // 2 + 10, // 3 + 10, // 4 + 10, // 5 + 30, // 6 + 1000, // 7 + 10, // 8 + ]; + let degrees_answer = [ + 2, // 0 + 2, // 1 + 2, // 2 + 3, // 3 + 4, // 4 + 5, // 5 + 30, // 6 + 173, // 7 + 10, // 8 + ]; + let ranges = [ + (-1.0, 1.0), // 0 + (-1.0, 1.0), // 1 + (-1.0, 1.0), // 2 + (-1.0, 1.0), // 3 + (-1.0, 1.0), // 4 + (-1.0, 1.0), // 5 + (-1.0, 1.0), // 6 + (-2.34567, 12.34567), // 7 + (-0.995 * PI, 0.995 * PI), // 8 + ]; + let nn_max = 400; + let tol = 1e-7; + for (index, f) in data_generators.into_iter().enumerate() { + // generate data + let (xa, xb) = ranges[index]; + let dx = xb - xa; + let nn_fit = degrees_fit[index]; + let np_fit = nn_fit + 1; + let zz = InterpChebyshev::points(nn_fit); + let mut xx_dat = Vector::new(np_fit); + let mut uu = Vector::new(np_fit); + let dy = if index == 7 { 0.01 } else { 0.1 }; + for i in 0..np_fit { + let x = (xb + xa + dx * zz[i]) / 2.0; + let noise = if i % 2 == 0 { dy } else { -dy }; + xx_dat[i] = x; + uu[i] = f(x) + noise; + } + + // adaptive interpolation + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let nn = interp.get_degree(); + + // check adapted degrees + print!("{:0>3}: N = {}", index, nn); + if nn > 0 { + let mut a = Vector::new(nn + 1); + chebyshev_coefficients(a.as_mut_data(), uu.as_data(), nn); + println!(", an = {}", a[nn]); + } else { + println!(); + } + assert_eq!(nn, degrees_answer[index]); + + // plot f(x) + /* + let (nstation, fig_width) = if index == 7 { (1201, 1200.0) } else { (201, 600.0) }; + let xx = Vector::linspace(xa, xb, nstation).unwrap(); + let yy_ana = xx.get_mapped(|x| f(x)); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_ana = Curve::new(); + let mut curve_int = Curve::new(); + let mut curve_dat = Curve::new(); + curve_ana.set_label("generator"); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_dat + .set_label("noisy data") + .set_line_style("None") + .set_marker_style("+"); + curve_ana.draw(xx.as_data(), yy_ana.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + curve_dat.draw(xx_dat.as_data(), uu.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_ana) + .add(&curve_int) + .add(&curve_dat) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .set_figure_size_points(fig_width, 500.0) + .save(&format!( + "/tmp/russell_lab/test_interp_chebyshev_new_adapt_noisy_uu_{:0>3}.svg", index )) .unwrap(); From 164dcb2238c2229130297a305e42d9a1d0cd640d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 10:12:28 +1000 Subject: [PATCH 63/93] Add examples to Readme --- russell_lab/README.md | 38 ++ .../figures/algo_interp_chebyshev_data.svg | 324 ++++++++---------- .../examples/algo_interp_chebyshev_data.rs | 24 +- 3 files changed, 208 insertions(+), 178 deletions(-) diff --git a/russell_lab/README.md b/russell_lab/README.md index 627b3764..b335d790 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -20,6 +20,9 @@ _This crate is part of [Russell - Rust Scientific Library](https://github.com/cp - [Check first and second derivatives](#check-first-and-second-derivatives) - [Bessel functions](#bessel-functions) - [Linear fitting](#linear-fitting) + - [Chebyshev adaptive interpolation (given function)](#chebyshev-adaptive-interpolation-given-function) + - [Chebyshev adaptive interpolation (given data)](#chebyshev-adaptive-interpolation-given-data) + - [Chebyshev adaptive interpolation (given noisy data)](#chebyshev-adaptive-interpolation-given-noisy-data) - [Lagrange interpolation](#lagrange-interpolation) - [Solution of a 1D PDE using spectral collocation](#solution-of-a-1d-pde-using-spectral-collocation) - [Numerical integration: perimeter of ellipse](#numerical-integration-perimeter-of-ellipse) @@ -325,6 +328,41 @@ Results: +### Chebyshev adaptive interpolation (given function) + +This example illustrates the use of `InterpChebyshev` to interpolate data given a function. + +[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_interp_chebyshev_adapt.rs) + +Results: + +![Chebyshev interpolation (given function)](data/figures/algo_interp_chebyshev_adapt.svg) + + + +### Chebyshev adaptive interpolation (given data) + +This example illustrates the use of `InterpChebyshev` to interpolate discrete data. + +[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_interp_chebyshev_data.rs) + +Results: + +![Chebyshev interpolation (given data)](data/figures/algo_interp_chebyshev_data.svg) + + + +### Chebyshev adaptive interpolation (given noisy data) + +This example illustrates the use of `InterpChebyshev` to interpolate noisy data. + +[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs) + +Results: + +![Chebyshev interpolation (given noisy data)](data/figures/algo_interp_chebyshev_noisy_data.svg) + + ### Lagrange interpolation diff --git a/russell_lab/data/figures/algo_interp_chebyshev_data.svg b/russell_lab/data/figures/algo_interp_chebyshev_data.svg index 2f122b73..88393886 100644 --- a/russell_lab/data/figures/algo_interp_chebyshev_data.svg +++ b/russell_lab/data/figures/algo_interp_chebyshev_data.svg @@ -6,7 +6,7 @@ - 2024-06-26T09:58:06.296924 + 2024-06-26T10:09:45.930534 image/svg+xml @@ -42,16 +42,16 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - - + @@ -97,11 +97,11 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -143,11 +143,11 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -184,11 +184,11 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -236,11 +236,11 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -297,11 +297,11 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -358,16 +358,16 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - - + @@ -391,11 +391,11 @@ z +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -410,11 +410,11 @@ L 399.740313 211.640365 +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -429,11 +429,11 @@ L 399.740313 163.256365 +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -447,11 +447,11 @@ L 399.740313 114.872365 +" clip-path="url(#pc10a815d94)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -521,76 +521,41 @@ z - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + +" clip-path="url(#pc10a815d94)" style="fill: none; stroke: #808080; stroke-width: 1.5; stroke-linecap: square"/> - + +" clip-path="url(#pc10a815d94)" style="fill: none; stroke: #808080; stroke-width: 1.5; stroke-linecap: square"/> - - + - + - + + + + + + + + + + + + + + + + + + - - - + diff --git a/russell_lab/examples/algo_interp_chebyshev_data.rs b/russell_lab/examples/algo_interp_chebyshev_data.rs index 245d415b..628b59d0 100644 --- a/russell_lab/examples/algo_interp_chebyshev_data.rs +++ b/russell_lab/examples/algo_interp_chebyshev_data.rs @@ -3,31 +3,45 @@ use russell_lab::*; fn main() -> Result<(), StrError> { // data - let uu = [3.0, 0.5, -4.5, -7.0]; let (xa, xb) = (0.0, 1.0); + let dx = xb - xa; + let uu = Vector::from(&[3.0, 0.5, -4.5, -7.0]); + let np = uu.dim(); // number of points + let nn = np - 1; // degree + let mut xx_dat = Vector::new(np); + let zz = InterpChebyshev::points(nn); + for i in 0..np { + xx_dat[i] = (xb + xa + dx * zz[i]) / 2.0; + } // interpolant let nn_max = 100; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, &uu)?; + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data())?; let nn = interp.get_degree(); // plot let xx = Vector::linspace(xa, xb, 201).unwrap(); let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_dat = Curve::new(); let mut curve_int = Curve::new(); + curve_dat + .set_label("data") + .set_line_style("None") + .set_marker_style("o") + .set_marker_void(true); curve_int .set_label(&format!("interpolated,N={}", nn)) - .set_line_style(":") - .set_marker_style(".") .set_marker_every(5); + curve_dat.draw(xx_dat.as_data(), uu.as_data()); curve_int.draw(xx.as_data(), yy_int.as_data()); let mut plot = Plot::new(); let mut legend = Legend::new(); legend.set_num_col(4); legend.set_outside(true); legend.draw(); - plot.add(&curve_int) + plot.add(&curve_dat) + .add(&curve_int) .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") From 30f80cf0599c5f0d6a260f40cf10149605de1f57 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 10:16:08 +1000 Subject: [PATCH 64/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 38da1d84..be211634 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -830,6 +830,17 @@ mod tests { } } + #[test] + fn new_adapt_uu_captures_errors() { + let uu = []; + let nn_max = 4; + let tol = 1e-7; + assert_eq!( + InterpChebyshev::new_adapt_uu(nn_max, tol, 0.0, 1.0, &uu).err(), + Some("the number of points = uu.len() must be ≥ 1") + ); + } + #[test] fn new_adapt_uu_works() { let data_generators = [ From 8e060b4234e43e9ee94d10a8fecdb35408fb138a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 10:17:24 +1000 Subject: [PATCH 65/93] Simplify test --- russell_lab/src/algo/interp_chebyshev.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index be211634..1b748fe5 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -1030,13 +1030,9 @@ mod tests { // check adapted degrees print!("{:0>3}: N = {}", index, nn); - if nn > 0 { - let mut a = Vector::new(nn + 1); - chebyshev_coefficients(a.as_mut_data(), uu.as_data(), nn); - println!(", an = {}", a[nn]); - } else { - println!(); - } + let mut a = Vector::new(nn + 1); + chebyshev_coefficients(a.as_mut_data(), uu.as_data(), nn); + println!(", an = {}", a[nn]); assert_eq!(nn, degrees_answer[index]); // plot f(x) From 0ed58b10d548b4ee4c9176ada3877dc096f42514 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 10:41:25 +1000 Subject: [PATCH 66/93] Handle linear functions in MultiRootSolverCheby --- ...ulti_root_solver_cheby_linear_function.svg | 920 ++++++++++++++++++ .../src/algo/multi_root_solver_cheby.rs | 123 ++- 2 files changed, 1032 insertions(+), 11 deletions(-) create mode 100644 russell_lab/data/figures/test_multi_root_solver_cheby_linear_function.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_linear_function.svg b/russell_lab/data/figures/test_multi_root_solver_cheby_linear_function.svg new file mode 100644 index 00000000..301b76b4 --- /dev/null +++ b/russell_lab/data/figures/test_multi_root_solver_cheby_linear_function.svg @@ -0,0 +1,920 @@ + + + + + + + + 2024-06-26T10:36:28.301076 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index ad728688..229c7309 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -85,19 +85,21 @@ impl MultiRootSolverCheby { /// /// # Input /// - /// * `nn` -- polynomial degree N (must be ≥ 2) + /// * `nn` -- polynomial degree N (must be ≥ 1) pub fn new(nn: usize) -> Result { // check - if nn < 2 { - return Err("the degree N must be ≥ 2"); + if nn < 1 { + return Err("the degree N must be ≥ 1"); } // companion matrix (except last row) let mut aa = Matrix::new(nn, nn); - aa.set(0, 1, 1.0); - for r in 1..(nn - 1) { - aa.set(r, r + 1, 0.5); // upper diagonal - aa.set(r, r - 1, 0.5); // lower diagonal + if nn > 1 { + aa.set(0, 1, 1.0); + for r in 1..(nn - 1) { + aa.set(r, r + 1, 0.5); // upper diagonal + aa.set(r, r - 1, 0.5); // lower diagonal + } } // done @@ -171,6 +173,19 @@ impl MultiRootSolverCheby { return Err("the trailing Chebyshev coefficient vanishes; try a smaller degree N"); } + // linear function + let (xa, xb, dx) = interp.get_range(); + if nn == 1 { + let z = -a[0] / a[1]; + let nr = if f64::abs(z) <= 1.0 + self.tol_abs_boundary { + self.roots[0] = (xb + xa + dx * z) / 2.0; + 1 + } else { + 0 + }; + return Ok(&self.roots.as_data()[..nr]); + } + // last row of the companion matrix for k in 0..nn { self.aa.set(nn - 1, k, -0.5 * a[k] / an); @@ -181,7 +196,6 @@ impl MultiRootSolverCheby { mat_eigenvalues(&mut self.l_real, &mut self.l_imag, &mut self.aa).unwrap(); // roots = real eigenvalues within the interval - let (xa, xb, dx) = interp.get_range(); let mut nroot = 0; for i in 0..nn { if f64::abs(self.l_imag[i]) < self.tol_rel_imag * f64::abs(self.l_real[i]) { @@ -383,8 +397,8 @@ mod tests { #[test] fn new_captures_errors() { - let nn = 1; - assert_eq!(MultiRootSolverCheby::new(nn).err(), Some("the degree N must be ≥ 2")); + let nn = 0; + assert_eq!(MultiRootSolverCheby::new(nn).err(), Some("the degree N must be ≥ 1")); } #[test] @@ -549,8 +563,8 @@ mod tests { if *id == 9 { assert_eq!(roots_unpolished.len(), 93); } - // figure /* + // figure let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; graph( &format!("test_multi_root_solver_cheby_{:0>3}", id), @@ -597,4 +611,91 @@ mod tests { Some("Newton's method did not converge") ); } + + #[test] + fn linear_function_no_roots_works() { + // data + let (xa, xb) = (0.0, 1.0); + let uu = Vector::from(&[3.0, 0.5]); + + // interpolant + let nn_max = 100; + let tol = 1e-8; + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let nn = interp.get_degree(); + + // find all roots in the interval + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots = Vector::from(&solver.find(&interp).unwrap()); + let nroot = roots.dim(); + assert_eq!(nroot, 0) + } + + #[test] + fn linear_function_works() { + // data + let (xa, xb) = (0.0, 1.0); + let dx = xb - xa; + let uu = Vector::from(&[3.0, 0.5, -4.5, -7.0]); + let np = uu.dim(); // number of points + let nn = np - 1; // degree + let mut xx_dat = Vector::new(np); + let zz = InterpChebyshev::points(nn); + for i in 0..np { + xx_dat[i] = (xb + xa + dx * zz[i]) / 2.0; + } + + // interpolant + let nn_max = 100; + let tol = 1e-8; + let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let nn = interp.get_degree(); + + // find all roots in the interval + let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let roots = Vector::from(&solver.find(&interp).unwrap()); + let f_at_roots = roots.get_mapped(|x| interp.eval(x).unwrap()); + let nroot = roots.dim(); + println!("roots =\n{}", roots); + for i in 0..nroot { + println!("xr = {}, f(xr) = {:.2e}", roots[i], f_at_roots[i]); + } + assert_eq!(nroot, 1); + approx_eq(roots[0], 0.7, 1e-15); + approx_eq(f_at_roots[0], 0.0, 1e-15); + + // plot + /* + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_dat = Curve::new(); + let mut curve_int = Curve::new(); + let mut curve_xr = Curve::new(); + curve_dat.set_label("data").set_line_style("None").set_marker_style("."); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_marker_every(5); + curve_xr + .set_label("root") + .set_line_style("None") + .set_marker_style("o") + .set_marker_void(true); + curve_dat.draw(xx_dat.as_data(), uu.as_data()); + curve_int.draw(xx.as_data(), yy_int.as_data()); + curve_xr.draw(roots.as_data(), f_at_roots.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_int) + .add(&curve_dat) + .add(&curve_xr) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/test_multi_root_solver_cheby_linear_function.svg") + .unwrap(); + */ + } } From 293f29c97c2f3f6a489b866e782182ebbde956cb Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 12:03:09 +1000 Subject: [PATCH 67/93] Make the reverse U vector internal to InterpChebyshev --- russell_lab/data/figures/test_new_with_uu.svg | 240 +++++++++--------- russell_lab/src/algo/interp_chebyshev.rs | 76 +++--- .../src/algo/multi_root_solver_cheby.rs | 4 +- russell_lab/src/math/chebyshev.rs | 207 +-------------- 4 files changed, 166 insertions(+), 361 deletions(-) diff --git a/russell_lab/data/figures/test_new_with_uu.svg b/russell_lab/data/figures/test_new_with_uu.svg index f0b821c3..d91ccc50 100644 --- a/russell_lab/data/figures/test_new_with_uu.svg +++ b/russell_lab/data/figures/test_new_with_uu.svg @@ -6,7 +6,7 @@ - 2024-06-25T17:48:19.778178 + 2024-06-26T11:48:51.565868 image/svg+xml @@ -42,16 +42,16 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - - + @@ -97,11 +97,11 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -143,11 +143,11 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -184,11 +184,11 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -236,11 +236,11 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -297,11 +297,11 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -358,16 +358,16 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - - + @@ -381,11 +381,11 @@ L -3.5 0 +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -400,11 +400,11 @@ L 397.723125 233.816365 +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -419,11 +419,11 @@ L 397.723125 183.416365 +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -472,11 +472,11 @@ z +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -491,11 +491,11 @@ L 397.723125 82.616365 +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 2.96,1.28; stroke-dashoffset: 0; stroke: #808080; stroke-width: 0.8"/> - + @@ -634,51 +634,54 @@ L 344.155125 56.923845 L 363.634398 49.180897 L 381.490398 42.296365 L 381.490398 42.296365 -" clip-path="url(#pd3de1e6443)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke-dasharray: 1.5,2.475; stroke-dashoffset: 0; stroke: #ff7f0e; stroke-width: 1.5"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke: #808080; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#pa95b1753bb)" style="fill: none; stroke: #808080; stroke-width: 1.5; stroke-linecap: square"/> - - + - - + + - - + - + diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 1b748fe5..f8205d29 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -1,4 +1,4 @@ -use crate::math::{chebyshev_lobatto_points_standard, chebyshev_tn, PI}; +use crate::math::{chebyshev_lobatto_points, chebyshev_tn, PI}; use crate::StrError; use crate::{NoArgs, Vector}; @@ -41,7 +41,6 @@ pub const TOL_RANGE: f64 = 1.0e-5; /// the derivative matrices, whereas this structure does not. /// 2. The [crate::InterpLagrange] renders the same results when using Chebyshev-Gauss-Lobatto points. /// 3. Only Chebyshev-Gauss-Lobatto points are considered here. -/// 4. The Chebyshev-Gauss-Lobatto coordinates are sorted from +1 to -1 (as in Reference # 1). /// /// # References /// @@ -63,11 +62,15 @@ pub struct InterpChebyshev { /// Holds the difference xb - xa dx: f64, - /// Holds the expansion coefficients (standard Chebyshev-Gauss-Lobatto) + /// Holds the expansion coefficients + /// + /// (associated with the reversed (from 1 to -1) Chebyshev-Gauss-Lobatto points) a: Vector, - /// Holds the function evaluation at the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points - uu: Vector, + /// Holds the reversed function evaluations + /// + /// (associated with the reversed (from 1 to -1) Chebyshev-Gauss-Lobatto points) + uu_rev: Vector, /// Holds the constant y=c value for a zeroth-order function constant_fx: f64, @@ -77,13 +80,13 @@ pub struct InterpChebyshev { } impl InterpChebyshev { - /// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto points + /// Returns the Chebyshev-Gauss-Lobatto points (from -1 to 1) /// /// # Input /// /// * `nn` -- polynomial degree N pub fn points(nn: usize) -> Vector { - chebyshev_lobatto_points_standard(nn) + chebyshev_lobatto_points(nn) } /// Allocates a new instance with uninitialized values @@ -108,7 +111,7 @@ impl InterpChebyshev { xb, dx: xb - xa, a: Vector::new(np), - uu: Vector::new(np), + uu_rev: Vector::new(np), constant_fx: 0.0, ready: false, }) @@ -153,9 +156,9 @@ impl InterpChebyshev { /// } /// ``` pub fn set_uu_value(&mut self, i: usize, uui: f64) { - self.uu[i] = uui; + self.uu_rev[self.nn - i] = uui; if i == self.nn { - chebyshev_coefficients(self.a.as_mut_data(), self.uu.as_mut_data(), self.nn); + chebyshev_coefficients(self.a.as_mut_data(), self.uu_rev.as_mut_data(), self.nn); self.ready = true; } else { self.ready = false; @@ -207,15 +210,15 @@ impl InterpChebyshev { xb, dx: xb - xa, a: Vector::new(np), - uu: Vector::new(np), + uu_rev: Vector::new(np), constant_fx: 0.0, ready: true, }; if nn == 0 { interp.constant_fx = f((xa + xb) / 2.0, args)?; } else { - chebyshev_data_vector(interp.uu.as_mut_data(), nn, xa, xb, args, &mut f)?; - chebyshev_coefficients(interp.a.as_mut_data(), interp.uu.as_mut_data(), nn); + chebyshev_data_vector(interp.uu_rev.as_mut_data(), nn, xa, xb, args, &mut f)?; + chebyshev_coefficients(interp.a.as_mut_data(), interp.uu_rev.as_mut_data(), nn); } Ok(interp) } @@ -227,7 +230,7 @@ impl InterpChebyshev { /// * `xa` -- lower bound /// * `xb` -- upper bound (> xa + ϵ) /// * `uu` -- the data vector such that `Uᵢ = f(xᵢ)`; i.e., the function evaluated at the - /// **standard** (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates. These coordinates + /// Chebyshev-Gauss-Lobatto coordinates (from -1 to 1). These coordinates /// are available via the [InterpChebyshev::points()] function. pub fn new_with_uu(xa: f64, xb: f64, uu: &[f64]) -> Result { if xb <= xa + TOL_RANGE { @@ -245,14 +248,17 @@ impl InterpChebyshev { xb, dx: xb - xa, a: Vector::new(np), - uu: Vector::from(&uu), + uu_rev: Vector::new(np), constant_fx: 0.0, ready: true, }; + for i in 0..np { + interp.uu_rev[nn - i] = uu[i]; + } if nn == 0 { interp.constant_fx = uu[0]; } else { - chebyshev_coefficients(interp.a.as_mut_data(), interp.uu.as_mut_data(), nn); + chebyshev_coefficients(interp.a.as_mut_data(), interp.uu_rev.as_mut_data(), nn); } Ok(interp) } @@ -320,11 +326,11 @@ impl InterpChebyshev { } let np_max = nn_max + 1; let mut work_a = vec![0.0; np_max]; - let mut work_uu = vec![0.0; np_max]; + let mut work_uu_rev = vec![0.0; np_max]; let mut an_prev = 0.0; for nn in 1..=nn_max { - chebyshev_data_vector(&mut work_uu, nn, xa, xb, args, &mut f)?; - chebyshev_coefficients(&mut work_a, &work_uu, nn); + chebyshev_data_vector(&mut work_uu_rev, nn, xa, xb, args, &mut f)?; + chebyshev_coefficients(&mut work_a, &work_uu_rev, nn); let an = work_a[nn]; if nn > 1 && f64::abs(an_prev) < tol && f64::abs(an) < tol { let nn_final = nn - 2; // -2 because the last two coefficients are zero @@ -343,7 +349,9 @@ impl InterpChebyshev { /// * `tol` -- tolerance to truncate the Chebyshev series (e.g., 1e-8) /// * `xa` -- lower bound /// * `xb` -- upper bound (> xa + ϵ) - /// * `uu` -- the data vector (len > 0) + /// * `uu` -- the data vector such that `Uᵢ = f(xᵢ)`; i.e., the function evaluated at the + /// Chebyshev-Gauss-Lobatto coordinates (from -1 to 1). These coordinates + /// are available via the [InterpChebyshev::points()] function. /// /// # Method /// @@ -356,7 +364,7 @@ impl InterpChebyshev { /// /// fn main() -> Result<(), StrError> { /// // data - /// let uu = [3.0, 0.5, -4.5, -7.0]; + /// let uu = [-7.0, -4.0, 0.5, 3.0]; /// let (xa, xb) = (0.0, 1.0); /// /// // interpolant @@ -471,7 +479,7 @@ impl InterpChebyshev { /// Computes the data vector (function evaluations at Chebyshev-Gauss-Lobatto points) fn chebyshev_data_vector( - work_uu: &mut [f64], + work_uu_rev: &mut [f64], nn: usize, xa: f64, xb: f64, @@ -485,31 +493,31 @@ where let np = nn + 1; assert!(nn > 0); assert!(xb > xa); - assert!(work_uu.len() >= np); + assert!(work_uu_rev.len() >= np); - // data vector U + // reverse data vector U (associated to Chebyshev-Gauss-Lobatto points from 1 to -1) let nf = nn as f64; - let uu = &mut work_uu[0..np]; + let uu_rev = &mut work_uu_rev[0..np]; for k in 0..np { let kf = k as f64; let x = (xb + xa + (xb - xa) * f64::cos(PI * kf / nf)) / 2.0; - uu[k] = (*f)(x, args)?; + uu_rev[k] = (*f)(x, args)?; } Ok(()) } /// Computes the Chebyshev-Gauss-Lobatto coefficients -fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { +fn chebyshev_coefficients(work_a: &mut [f64], work_uu_rev: &[f64], nn: usize) { // check let np = nn + 1; assert!(nn > 0); assert!(work_a.len() >= np); - assert!(work_uu.len() >= np); + assert!(work_uu_rev.len() >= np); // coefficients a let nf = nn as f64; let a = &mut work_a[0..np]; - let uu = &work_uu[0..np]; + let uu_rev = &work_uu_rev[0..np]; for j in 0..np { let jf = j as f64; let qj = if j == 0 || j == nn { 2.0 } else { 1.0 }; @@ -517,7 +525,7 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu: &[f64], nn: usize) { for k in 0..np { let kf = k as f64; let qk = if k == 0 || k == nn { 2.0 } else { 1.0 }; - a[j] += uu[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); + a[j] += uu_rev[k] * 2.0 * f64::cos(PI * jf * kf / nf) / (qj * qk * nf); } } } @@ -553,7 +561,7 @@ mod tests { assert_eq!(interp.nn, nn); assert_eq!(interp.np, np); assert_eq!(interp.a.dim(), np); - assert_eq!(interp.uu.dim(), np); + assert_eq!(interp.uu_rev.dim(), np); assert_eq!(interp.constant_fx, 0.0); assert_eq!(interp.ready, false); } @@ -617,7 +625,7 @@ mod tests { let f = |x, _: &mut NoArgs| Ok(-1.0 + f64::sqrt(1.0 + 2.0 * x * 1200.0)); let (xa, xb) = (0.0, 1.0); - let nn = 10; + let nn = 6; let zz = InterpChebyshev::points(nn); let dx = xb - xa; let args = &mut 0; @@ -632,11 +640,11 @@ mod tests { for i in 0..np { let x = (xb + xa + dx * zz[i]) / 2.0; let fxi = interp.eval(x).unwrap(); - approx_eq(fxi, interp.uu[i], 1e-13); + approx_eq(fxi, interp.uu_rev[nn - i], 1e-13); } let err = interp.estimate_max_error(100, args, f).unwrap(); println!("err = {}", err); - assert!(err < 0.73); + approx_eq(err, 1.74, 1e-3); // plot f(x) /* diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 229c7309..9fd07d78 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -616,7 +616,7 @@ mod tests { fn linear_function_no_roots_works() { // data let (xa, xb) = (0.0, 1.0); - let uu = Vector::from(&[3.0, 0.5]); + let uu = Vector::from(&[0.5, 3.0]); // interpolant let nn_max = 100; @@ -636,7 +636,7 @@ mod tests { // data let (xa, xb) = (0.0, 1.0); let dx = xb - xa; - let uu = Vector::from(&[3.0, 0.5, -4.5, -7.0]); + let uu = Vector::from(&[-7.0, -4.5, 0.5, 3.0]); let np = uu.dim(); // number of points let nn = np - 1; // degree let mut xx_dat = Vector::new(np); diff --git a/russell_lab/src/math/chebyshev.rs b/russell_lab/src/math/chebyshev.rs index 38e21805..7c151e0d 100644 --- a/russell_lab/src/math/chebyshev.rs +++ b/russell_lab/src/math/chebyshev.rs @@ -330,114 +330,11 @@ pub fn chebyshev_lobatto_points(nn: usize) -> Vector { xx } -/// Returns the standard (from 1 to -1) Chebyshev-Gauss-Lobatto coordinates -/// -/// The point coordinates are defined by: -/// -/// ```text -/// ⎛ j⋅π ⎞ -/// Xⱼ = cos ⎜ ————— ⎟ -/// ⎝ N ⎠ -/// -/// j = 0 ... N -/// ``` -/// -/// Nonetheless, here, the coordinates are calculates by using the sin(x) function: -/// -/// ```text -/// ⎛ π⋅(N-2j) ⎞ -/// Xⱼ = sin ⎜ —————————— ⎟ -/// ⎝ 2⋅N ⎠ -/// -/// j = 0 ... N -/// ``` -/// -/// See equation (2.4.14) on page 86 of the Reference #1 (with the "standard" ordering of nodes from +1 to -1). -/// See also equation (1) of the Reference #2 -/// -/// **Note:** This function enforces the symmetry of the sequence of points. -/// -/// # Input -/// -/// * `nn` -- the polynomial degree `N`; thus the number of points is `npoint = nn + 1`. -/// -/// # Input -/// -/// * The number of points will be equal to `npoint = yy.len()` and the -/// polynomial degree will be equal to `nn = npoint - 1` -/// -/// # Output -/// -/// * `yy` -- is the array holding the coordinates (from 1 to -1). Its length is equal -/// to `npoint = nn + 1` where `nn` is the polynomial degree. Requirement: `npoint ≥ 2`. -/// -/// # Notes -/// -/// See the note below from Reference # 1 (page 86): -/// -/// "Note that the Chebyshev quadrature points as just defined are ordered -/// from right to left. This violates our general convention that quadrature points -/// are ordered from left to right (see Sect. 2.2.3). Virtually all of the classical -/// literature on Chebyshev spectral methods uses this reversed order. Therefore, -/// in the special case of the Chebyshev quadrature points we shall adhere to the -/// ordering convention that is widely used in the literature (and implemented -/// in the available software). We realize that our resolution of this dilemma -/// imposes upon the reader the task of mentally reversing the ordering of the -/// Chebyshev nodes whenever they are used in general formulas for orthogonal -/// polynomials." (Canuto, Hussaini, Quarteroni, Zang) -/// -/// # References -/// -/// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in -/// Single Domains. Springer. 563p -/// 2. Baltensperger R and Trummer MR (2003) Spectral differencing with a twist, -/// SIAM Journal Scientific Computation 24(5):1465-1487 -/// -/// # Examples -/// -/// ![Chebyshev points](https://raw.githubusercontent.com/cpmech/russell/main/russell_lab/data/figures/math_chebyshev_points.svg) -/// -/// ``` -/// use russell_lab::math; -/// -/// let xx = math::chebyshev_lobatto_points_standard(8); -/// println!("\nChebyshev-Gauss-Lobatto points =\n{:.3?}\n", xx.as_data()); -/// ``` -/// -/// The output looks like: -/// -/// ```text -/// Chebyshev-Gauss-Lobatto points = -/// [1.000, 0.924, 0.707, 0.383, 0.000, -0.383, -0.707, -0.924, -1.000] -/// ``` -pub fn chebyshev_lobatto_points_standard(nn: usize) -> Vector { - let mut xx = Vector::new(nn + 1); - xx[0] = 1.0; - xx[nn] = -1.0; - if nn < 3 { - return xx; - } - let nf = nn as f64; - let d = 2.0 * nf; - let l = if (nn & 1) == 0 { - // even number of segments - nn / 2 - } else { - // odd number of segments - (nn + 3) / 2 - 1 - }; - for i in 1..l { - xx[nn - i] = -f64::sin(PI * (nf - 2.0 * (i as f64)) / d); - xx[i] = -xx[nn - i]; - } - return xx; -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::{chebyshev_gauss_points, chebyshev_lobatto_points, chebyshev_lobatto_points_standard}; + use super::{chebyshev_gauss_points, chebyshev_lobatto_points}; use super::{chebyshev_tn, chebyshev_tn_deriv1, chebyshev_tn_deriv2}; use crate::math::{SQRT_2, SQRT_3}; use crate::{approx_eq, vec_approx_eq, Vector}; @@ -740,106 +637,4 @@ mod tests { vec_approx_eq(&xx, &xx_ref, 1e-15); check_segment_symmetry(&xx); } - - #[test] - fn chebyshev_lobatto_points_standard_works() { - let xx = chebyshev_lobatto_points_standard(0); - assert_eq!(xx.as_data(), &[-1.0]); - - let xx = chebyshev_lobatto_points_standard(1); - let xx_ref = vec![1.0, -1.0]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(2); - let xx_ref = vec![1.0, 0.0, -1.0]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(3); - let xx_ref = vec![1.0, 0.5, -0.5, -1.0]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(4); - let xx_ref = vec![ - 1.000000000000000e+00, - 7.071067811865476e-01, - 0.0, - -7.071067811865475e-01, - -1.000000000000000e+00, - ]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(5); - let xx_ref = vec![ - 1.000000000000000e+00, - 8.090169943749475e-01, - 3.090169943749475e-01, - -3.090169943749473e-01, - -8.090169943749473e-01, - -1.000000000000000e+00, - ]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(6); - let xx_ref = vec![ - 1.000000000000000e+00, - 8.660254037844387e-01, - 5.000000000000001e-01, - 0.0, - -4.999999999999998e-01, - -8.660254037844385e-01, - -1.000000000000000e+00, - ]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(7); - let xx_ref = vec![ - 1.000000000000000e+00, - 9.009688679024191e-01, - 6.234898018587336e-01, - 2.225209339563144e-01, - -2.225209339563143e-01, - -6.234898018587335e-01, - -9.009688679024190e-01, - -1.000000000000000e+00, - ]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(8); - let xx_ref = vec![ - 1.000000000000000e+00, - 9.238795325112867e-01, - 7.071067811865476e-01, - 3.826834323650898e-01, - 0.0, - -3.826834323650897e-01, - -7.071067811865475e-01, - -9.238795325112867e-01, - -1.000000000000000e+00, - ]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - - let xx = chebyshev_lobatto_points_standard(9); - let xx_ref = vec![ - 1.000000000000000e+00, - 9.396926207859084e-01, - 7.660444431189780e-01, - 5.000000000000001e-01, - 1.736481776669304e-01, - -1.736481776669303e-01, - -4.999999999999998e-01, - -7.660444431189779e-01, - -9.396926207859083e-01, - -1.000000000000000e+00, - ]; - vec_approx_eq(&xx, &xx_ref, 1e-15); - check_segment_symmetry(&xx); - } } From 9509729198218d56e7a381bb041accf45f13db4d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 12:10:00 +1000 Subject: [PATCH 68/93] Use Vec instead of Vector in InterpChebyshev --- russell_lab/src/algo/interp_chebyshev.rs | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index f8205d29..9d0f98e3 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -65,12 +65,12 @@ pub struct InterpChebyshev { /// Holds the expansion coefficients /// /// (associated with the reversed (from 1 to -1) Chebyshev-Gauss-Lobatto points) - a: Vector, + a: Vec, /// Holds the reversed function evaluations /// /// (associated with the reversed (from 1 to -1) Chebyshev-Gauss-Lobatto points) - uu_rev: Vector, + uu_rev: Vec, /// Holds the constant y=c value for a zeroth-order function constant_fx: f64, @@ -110,8 +110,8 @@ impl InterpChebyshev { xa, xb, dx: xb - xa, - a: Vector::new(np), - uu_rev: Vector::new(np), + a: vec![0.0; np], + uu_rev: vec![0.0; np], constant_fx: 0.0, ready: false, }) @@ -158,7 +158,7 @@ impl InterpChebyshev { pub fn set_uu_value(&mut self, i: usize, uui: f64) { self.uu_rev[self.nn - i] = uui; if i == self.nn { - chebyshev_coefficients(self.a.as_mut_data(), self.uu_rev.as_mut_data(), self.nn); + chebyshev_coefficients(&mut self.a, &self.uu_rev, self.nn); self.ready = true; } else { self.ready = false; @@ -209,16 +209,16 @@ impl InterpChebyshev { xa, xb, dx: xb - xa, - a: Vector::new(np), - uu_rev: Vector::new(np), + a: vec![0.0; np], + uu_rev: vec![0.0; np], constant_fx: 0.0, ready: true, }; if nn == 0 { interp.constant_fx = f((xa + xb) / 2.0, args)?; } else { - chebyshev_data_vector(interp.uu_rev.as_mut_data(), nn, xa, xb, args, &mut f)?; - chebyshev_coefficients(interp.a.as_mut_data(), interp.uu_rev.as_mut_data(), nn); + chebyshev_data_vector(&mut interp.uu_rev, nn, xa, xb, args, &mut f)?; + chebyshev_coefficients(&mut interp.a, &interp.uu_rev, nn); } Ok(interp) } @@ -247,8 +247,8 @@ impl InterpChebyshev { xa, xb, dx: xb - xa, - a: Vector::new(np), - uu_rev: Vector::new(np), + a: vec![0.0; np], + uu_rev: vec![0.0; np], constant_fx: 0.0, ready: true, }; @@ -258,7 +258,7 @@ impl InterpChebyshev { if nn == 0 { interp.constant_fx = uu[0]; } else { - chebyshev_coefficients(interp.a.as_mut_data(), interp.uu_rev.as_mut_data(), nn); + chebyshev_coefficients(&mut interp.a, &interp.uu_rev, nn); } Ok(interp) } @@ -467,7 +467,7 @@ impl InterpChebyshev { } /// Returns an access to the expansion coefficients (a) - pub fn get_coefficients(&self) -> &Vector { + pub fn get_coefficients(&self) -> &Vec { &self.a } @@ -536,7 +536,7 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu_rev: &[f64], nn: usize) { mod tests { use super::{chebyshev_coefficients, InterpChebyshev}; use crate::math::PI; - use crate::{approx_eq, vec_approx_eq, NoArgs, Vector, TOL_RANGE}; + use crate::{approx_eq, array_approx_eq, NoArgs, Vector, TOL_RANGE}; #[allow(unused)] use plotpy::{Curve, Legend, Plot}; @@ -560,8 +560,8 @@ mod tests { assert_eq!(interp.dx, 8.0); assert_eq!(interp.nn, nn); assert_eq!(interp.np, np); - assert_eq!(interp.a.dim(), np); - assert_eq!(interp.uu_rev.dim(), np); + assert_eq!(interp.a.len(), np); + assert_eq!(interp.uu_rev.len(), np); assert_eq!(interp.constant_fx, 0.0); assert_eq!(interp.ready, false); } @@ -1139,6 +1139,6 @@ mod tests { assert_eq!(interp.get_degree(), 2); assert_eq!(interp.get_range(), (-4.0, 4.0, 8.0)); assert_eq!(interp.is_ready(), true); - vec_approx_eq(interp.get_coefficients(), &[7.0, 0.0, 8.0], 1e-15); + array_approx_eq(interp.get_coefficients(), &[7.0, 0.0, 8.0], 1e-15); } } From e108f1602aefacfe7629fbfd5dd0bcd895fe2a6a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 13:50:38 +1000 Subject: [PATCH 69/93] Rename figures --- ... test_interp_chebyshev_adapt_data_000.svg} | 0 ... test_interp_chebyshev_adapt_data_001.svg} | 0 ... test_interp_chebyshev_adapt_data_002.svg} | 0 ... test_interp_chebyshev_adapt_data_003.svg} | 0 ... test_interp_chebyshev_adapt_data_004.svg} | 0 ... test_interp_chebyshev_adapt_data_005.svg} | 0 ... test_interp_chebyshev_adapt_data_006.svg} | 0 ... test_interp_chebyshev_adapt_data_007.svg} | 0 ... test_interp_chebyshev_adapt_data_008.svg} | 0 ...interp_chebyshev_adapt_data_noisy_000.svg} | 0 ...interp_chebyshev_adapt_data_noisy_001.svg} | 0 ...interp_chebyshev_adapt_data_noisy_002.svg} | 0 ...interp_chebyshev_adapt_data_noisy_003.svg} | 0 ...interp_chebyshev_adapt_data_noisy_004.svg} | 0 ...interp_chebyshev_adapt_data_noisy_005.svg} | 0 ...interp_chebyshev_adapt_data_noisy_006.svg} | 0 ...interp_chebyshev_adapt_data_noisy_007.svg} | 0 ...interp_chebyshev_adapt_data_noisy_008.svg} | 0 ...st_interp_chebyshev_adapt_function_000.svg | 1087 ++++++++++++ ...st_interp_chebyshev_adapt_function_001.svg | 1093 ++++++++++++ ...st_interp_chebyshev_adapt_function_002.svg | 1261 +++++++++++++ ...st_interp_chebyshev_adapt_function_003.svg | 1239 +++++++++++++ ...st_interp_chebyshev_adapt_function_004.svg | 1177 ++++++++++++ ...st_interp_chebyshev_adapt_function_005.svg | 1219 +++++++++++++ ...st_interp_chebyshev_adapt_function_006.svg | 1442 +++++++++++++++ ...st_interp_chebyshev_adapt_function_007.svg | 1574 +++++++++++++++++ ...st_interp_chebyshev_adapt_function_008.svg | 1137 ++++++++++++ ...svg => test_interp_chebyshev_set_data.svg} | 0 28 files changed, 11229 insertions(+) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_000.svg => test_interp_chebyshev_adapt_data_000.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_001.svg => test_interp_chebyshev_adapt_data_001.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_002.svg => test_interp_chebyshev_adapt_data_002.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_003.svg => test_interp_chebyshev_adapt_data_003.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_004.svg => test_interp_chebyshev_adapt_data_004.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_005.svg => test_interp_chebyshev_adapt_data_005.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_006.svg => test_interp_chebyshev_adapt_data_006.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_007.svg => test_interp_chebyshev_adapt_data_007.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_uu_008.svg => test_interp_chebyshev_adapt_data_008.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_000.svg => test_interp_chebyshev_adapt_data_noisy_000.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_001.svg => test_interp_chebyshev_adapt_data_noisy_001.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_002.svg => test_interp_chebyshev_adapt_data_noisy_002.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_003.svg => test_interp_chebyshev_adapt_data_noisy_003.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_004.svg => test_interp_chebyshev_adapt_data_noisy_004.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_005.svg => test_interp_chebyshev_adapt_data_noisy_005.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_006.svg => test_interp_chebyshev_adapt_data_noisy_006.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_007.svg => test_interp_chebyshev_adapt_data_noisy_007.svg} (100%) rename russell_lab/data/figures/{test_interp_chebyshev_new_adapt_noisy_uu_008.svg => test_interp_chebyshev_adapt_data_noisy_008.svg} (100%) create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_000.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_001.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_002.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_003.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_004.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_005.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_006.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_007.svg create mode 100644 russell_lab/data/figures/test_interp_chebyshev_adapt_function_008.svg rename russell_lab/data/figures/{test_new_with_uu.svg => test_interp_chebyshev_set_data.svg} (100%) diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_000.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_000.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_000.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_000.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_001.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_001.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_001.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_001.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_002.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_002.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_002.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_002.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_003.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_003.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_003.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_003.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_004.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_004.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_004.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_004.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_005.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_005.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_005.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_005.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_006.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_006.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_006.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_006.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_007.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_007.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_007.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_007.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_008.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_008.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_uu_008.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_008.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_000.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_000.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_000.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_000.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_001.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_001.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_001.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_001.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_002.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_002.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_002.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_002.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_003.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_003.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_003.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_003.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_004.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_004.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_004.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_004.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_005.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_005.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_005.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_005.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_006.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_006.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_006.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_006.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_007.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_007.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_007.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_007.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_008.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_008.svg similarity index 100% rename from russell_lab/data/figures/test_interp_chebyshev_new_adapt_noisy_uu_008.svg rename to russell_lab/data/figures/test_interp_chebyshev_adapt_data_noisy_008.svg diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_000.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_000.svg new file mode 100644 index 00000000..ef0c1602 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_000.svg @@ -0,0 +1,1087 @@ + + + + + + + + 2024-06-26T13:41:13.455548 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_001.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_001.svg new file mode 100644 index 00000000..f9b665af --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_001.svg @@ -0,0 +1,1093 @@ + + + + + + + + 2024-06-26T13:41:13.813061 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_002.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_002.svg new file mode 100644 index 00000000..db598eff --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_002.svg @@ -0,0 +1,1261 @@ + + + + + + + + 2024-06-26T13:41:14.174017 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_003.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_003.svg new file mode 100644 index 00000000..6d5abc85 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_003.svg @@ -0,0 +1,1239 @@ + + + + + + + + 2024-06-26T13:41:14.515154 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_004.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_004.svg new file mode 100644 index 00000000..9ceafca4 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_004.svg @@ -0,0 +1,1177 @@ + + + + + + + + 2024-06-26T13:41:14.876382 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_005.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_005.svg new file mode 100644 index 00000000..551c5649 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_005.svg @@ -0,0 +1,1219 @@ + + + + + + + + 2024-06-26T13:41:15.227566 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_006.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_006.svg new file mode 100644 index 00000000..cb5a7286 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_006.svg @@ -0,0 +1,1442 @@ + + + + + + + + 2024-06-26T13:41:15.587493 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_007.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_007.svg new file mode 100644 index 00000000..228fa9ee --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_007.svg @@ -0,0 +1,1574 @@ + + + + + + + + 2024-06-26T13:41:15.959494 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_interp_chebyshev_adapt_function_008.svg b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_008.svg new file mode 100644 index 00000000..67a32c48 --- /dev/null +++ b/russell_lab/data/figures/test_interp_chebyshev_adapt_function_008.svg @@ -0,0 +1,1137 @@ + + + + + + + + 2024-06-26T13:41:16.325946 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_new_with_uu.svg b/russell_lab/data/figures/test_interp_chebyshev_set_data.svg similarity index 100% rename from russell_lab/data/figures/test_new_with_uu.svg rename to russell_lab/data/figures/test_interp_chebyshev_set_data.svg From b587c52432aa3e96436d78e02408b5f5eabef8b8 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 15:48:24 +1000 Subject: [PATCH 70/93] [Important] Rewrite InterpChebyshev methods --- russell_lab/benches/algo_chebyshev.rs | 6 +- russell_lab/examples/algo_interp_chebyshev.rs | 3 +- .../examples/algo_interp_chebyshev_adapt.rs | 3 +- .../examples/algo_interp_chebyshev_data.rs | 5 +- .../algo_interp_chebyshev_noisy_data.rs | 3 +- .../examples/algo_multi_root_solver_cheby.rs | 3 +- russell_lab/src/algo/interp_chebyshev.rs | 515 ++++++++---------- .../src/algo/multi_root_solver_cheby.rs | 29 +- russell_lab/zscripts/run-examples.bash | 2 +- 9 files changed, 260 insertions(+), 309 deletions(-) diff --git a/russell_lab/benches/algo_chebyshev.rs b/russell_lab/benches/algo_chebyshev.rs index 9769ec7f..4a0b0037 100644 --- a/russell_lab/benches/algo_chebyshev.rs +++ b/russell_lab/benches/algo_chebyshev.rs @@ -13,11 +13,13 @@ fn bench_chebyshev_eval(c: &mut Criterion) { for nn in &nns { group.throughput(Throughput::Elements(*nn as u64)); group.bench_with_input(BenchmarkId::new("clenshaw", nn), nn, |b, &nn| { - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); b.iter(|| interp.eval((xa + xb) / 2.0).unwrap()); }); group.bench_with_input(BenchmarkId::new("trigonometric", nn), nn, |b, &nn| { - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); b.iter(|| interp.eval_using_trig((xa + xb) / 2.0).unwrap()); }); } diff --git a/russell_lab/examples/algo_interp_chebyshev.rs b/russell_lab/examples/algo_interp_chebyshev.rs index 40941bbd..6531b7c3 100644 --- a/russell_lab/examples/algo_interp_chebyshev.rs +++ b/russell_lab/examples/algo_interp_chebyshev.rs @@ -9,7 +9,8 @@ fn main() -> Result<(), StrError> { // interpolant let degree = 10; let args = &mut 0; - let interp = InterpChebyshev::new_with_f(degree, xa, xb, args, f)?; + let mut interp = InterpChebyshev::new(degree, xa, xb)?; + interp.set_function(degree, args, f)?; approx_eq(interp.eval(0.0).unwrap(), 1.0, 1e-15); // plot diff --git a/russell_lab/examples/algo_interp_chebyshev_adapt.rs b/russell_lab/examples/algo_interp_chebyshev_adapt.rs index e02e013a..7721ecd5 100644 --- a/russell_lab/examples/algo_interp_chebyshev_adapt.rs +++ b/russell_lab/examples/algo_interp_chebyshev_adapt.rs @@ -10,7 +10,8 @@ fn main() -> Result<(), StrError> { let nn_max = 200; let tol = 1e-8; let args = &mut 0; - let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f)?; + let mut interp = InterpChebyshev::new(nn_max, xa, xb)?; + interp.adapt_function(tol, args, f)?; println!("N = {}", interp.get_degree()); // plot diff --git a/russell_lab/examples/algo_interp_chebyshev_data.rs b/russell_lab/examples/algo_interp_chebyshev_data.rs index 628b59d0..ebf93a65 100644 --- a/russell_lab/examples/algo_interp_chebyshev_data.rs +++ b/russell_lab/examples/algo_interp_chebyshev_data.rs @@ -5,7 +5,7 @@ fn main() -> Result<(), StrError> { // data let (xa, xb) = (0.0, 1.0); let dx = xb - xa; - let uu = Vector::from(&[3.0, 0.5, -4.5, -7.0]); + let uu = Vector::from(&[-7.0, -4.5, 0.5, 3.0]); let np = uu.dim(); // number of points let nn = np - 1; // degree let mut xx_dat = Vector::new(np); @@ -17,7 +17,8 @@ fn main() -> Result<(), StrError> { // interpolant let nn_max = 100; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data())?; + let mut interp = InterpChebyshev::new(nn_max, xa, xb)?; + interp.adapt_data(tol, uu.as_data())?; let nn = interp.get_degree(); // plot diff --git a/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs b/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs index c9941376..1d7fb873 100644 --- a/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs +++ b/russell_lab/examples/algo_interp_chebyshev_noisy_data.rs @@ -22,7 +22,8 @@ fn main() -> Result<(), StrError> { // interpolant let nn_max = 100; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data())?; + let mut interp = InterpChebyshev::new(nn_max, xa, xb)?; + interp.adapt_data(tol, uu.as_data())?; let nn = interp.get_degree(); // plot diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 12520af7..4ef1fa74 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -12,7 +12,8 @@ fn main() -> Result<(), StrError> { // adaptive interpolation let nn_max = 200; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f)?; + let mut interp = InterpChebyshev::new(nn_max, xa, xb)?; + interp.adapt_function(tol, args, f)?; let nn = interp.get_degree(); println!("N = {}", nn); diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 9d0f98e3..dde8ed39 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -47,11 +47,11 @@ pub const TOL_RANGE: f64 = 1.0e-5; /// 1. Canuto C, Hussaini MY, Quarteroni A, Zang TA (2006) Spectral Methods: Fundamentals in /// Single Domains. Springer. 563p pub struct InterpChebyshev { - /// Holds the polynomial degree N - nn: usize, + /// Holds the maximum polynomial degree N + nn_max: usize, - /// Holds the number of points (= N + 1) - np: usize, + /// Holds the actual polynomial degree N + nn: usize, /// Holds the lower bound xa: f64, @@ -65,17 +65,21 @@ pub struct InterpChebyshev { /// Holds the expansion coefficients /// /// (associated with the reversed (from 1 to -1) Chebyshev-Gauss-Lobatto points) + /// + /// len = nn_max + 1 a: Vec, /// Holds the reversed function evaluations /// /// (associated with the reversed (from 1 to -1) Chebyshev-Gauss-Lobatto points) + /// + /// len = nn_max + 1 uu_rev: Vec, /// Holds the constant y=c value for a zeroth-order function constant_fx: f64, - /// Indicates that `a` and `uu` are set and ready for `eval` + /// Indicates that the coefficients and data arrays are ready ready: bool, } @@ -89,91 +93,48 @@ impl InterpChebyshev { chebyshev_lobatto_points(nn) } - /// Allocates a new instance with uninitialized values - /// - /// **Important:** Make sure to call [InterpChebyshev::set_uu_value()] before - /// calling [InterpChebyshev::eval()] to evaluate the interpolated function. + /// Allocates a new instance /// /// # Input /// - /// * `nn` -- polynomial degree N + /// * `nn_max` -- maximum polynomial degree N_max. The actual degree will be calculated + /// after setting the data (via a function or directly using the methods listed below). /// * `xa` -- lower bound /// * `xb` -- upper bound (> xa + ϵ) - pub fn new(nn: usize, xa: f64, xb: f64) -> Result { + /// + /// # Notes + /// + /// This struct is ready for computations only after one of the following is called: + /// + /// * [InterpChebyshev::set_function()] -- sets the data using a function + /// * [InterpChebyshev::set_data()] -- sets the components of the U vector at grid points + /// * [InterpChebyshev::adapt_function()] -- performs adaptive interpolation for given function + /// * [InterpChebyshev::adapt_data()] -- performs adaptive interpolation for given data + pub fn new(nn_max: usize, xa: f64, xb: f64) -> Result { if xb <= xa + TOL_RANGE { return Err("xb must be greater than xa + ϵ"); } - let np = nn + 1; + let np_max = nn_max + 1; Ok(InterpChebyshev { - nn, - np, + nn_max, + nn: 0, xa, xb, dx: xb - xa, - a: vec![0.0; np], - uu_rev: vec![0.0; np], + a: vec![0.0; np_max], + uu_rev: vec![0.0; np_max], constant_fx: 0.0, ready: false, }) } - /// Sets a component of the U vector and computes the expansion coefficients - /// - /// **Note:** This function will compute the expansion coefficients when - /// the last component is set; i.e., when `i == nn` (nn is the degree N). - /// Therefore, it is recommended to call this function sequentially - /// from 0 to N (N is available via [InterpChebyshev::get_degree()]). - /// - /// # Input - /// - /// * `i` -- the index of the Chebyshev-Gauss-Lobatto point in `[0, N]` - /// with `N` being the polynomial degree. The grid points can be obtained - /// using the [InterpChebyshev::points()] function. - /// * `uui` -- the i-th component of the `U` vector; i.e., `Uᵢ` - /// - /// # Panics - /// - /// A panic will occur if `i` is out of range (it must be in `[0, N]`) - /// - /// # Examples - /// - /// ``` - /// use russell_lab::*; - /// - /// fn main() -> Result<(), StrError> { - /// let nn = 2; - /// let (xa, xb) = (-4.0, 4.0); - /// let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - /// let zz = InterpChebyshev::points(nn); - /// let dx = xb - xa; - /// let np = nn + 1; - /// for i in 0..np { - /// let x = (xb + xa + dx * zz[i]) / 2.0; - /// interp.set_uu_value(i, x * x - 1.0); - /// } - /// approx_eq(interp.eval(0.0)?, -1.0, 1e-15); - /// Ok(()) - /// } - /// ``` - pub fn set_uu_value(&mut self, i: usize, uui: f64) { - self.uu_rev[self.nn - i] = uui; - if i == self.nn { - chebyshev_coefficients(&mut self.a, &self.uu_rev, self.nn); - self.ready = true; - } else { - self.ready = false; - } - } - - /// Allocates a new instance with given f(x) function + /// Sets the data using a function /// /// # Input /// - /// * `nn` -- polynomial degree N - /// * `xa` -- lower bound - /// * `xb` -- upper bound (> xa + ϵ) + /// * `nn` -- polynomial degree (≤ nn_max) /// * `args` -- extra arguments for the f(x) function - /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + /// * `f` -- callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. /// /// # Examples /// @@ -188,91 +149,95 @@ impl InterpChebyshev { /// // interpolant /// let degree = 10; /// let args = &mut 0; - /// let interp = InterpChebyshev::new_with_f(degree, xa, xb, args, f)?; + /// let mut interp = InterpChebyshev::new(degree, xa, xb)?; + /// interp.set_function(degree, args, f)?; /// /// // check /// approx_eq(interp.eval(0.0).unwrap(), 1.0, 1e-15); /// Ok(()) /// } /// ``` - pub fn new_with_f(nn: usize, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result + pub fn set_function(&mut self, nn: usize, args: &mut A, mut f: F) -> Result<(), StrError> where F: FnMut(f64, &mut A) -> Result, { - if xb <= xa + TOL_RANGE { - return Err("xb must be greater than xa + ϵ"); + if nn > self.nn_max { + return Err("nn must be ≤ nn_max"); } - let np = nn + 1; - let mut interp = InterpChebyshev { - nn, - np, - xa, - xb, - dx: xb - xa, - a: vec![0.0; np], - uu_rev: vec![0.0; np], - constant_fx: 0.0, - ready: true, - }; - if nn == 0 { - interp.constant_fx = f((xa + xb) / 2.0, args)?; + self.nn = nn; + if self.nn == 0 { + self.constant_fx = f((self.xa + self.xb) / 2.0, args)?; } else { - chebyshev_data_vector(&mut interp.uu_rev, nn, xa, xb, args, &mut f)?; - chebyshev_coefficients(&mut interp.a, &interp.uu_rev, nn); + chebyshev_data_vector(&mut self.uu_rev, self.nn, self.xa, self.xb, args, &mut f)?; + chebyshev_coefficients(&mut self.a, &self.uu_rev, self.nn); } - Ok(interp) + self.ready = true; + Ok(()) } - /// Allocates a new instance with given U vector (function evaluated at grid points) + /// Sets the components of the U vector at grid points /// /// # Input /// - /// * `xa` -- lower bound - /// * `xb` -- upper bound (> xa + ϵ) - /// * `uu` -- the data vector such that `Uᵢ = f(xᵢ)`; i.e., the function evaluated at the - /// Chebyshev-Gauss-Lobatto coordinates (from -1 to 1). These coordinates - /// are available via the [InterpChebyshev::points()] function. - pub fn new_with_uu(xa: f64, xb: f64, uu: &[f64]) -> Result { - if xb <= xa + TOL_RANGE { - return Err("xb must be greater than xa + ϵ"); - } + /// * `uu` -- U vector with dim = npoint (≥ 1); thus the polynomial degree will be + /// `nn = npoint - 1` and must be ≤ nn_max + /// + /// # Examples + /// + /// ``` + /// use russell_lab::*; + /// + /// fn main() -> Result<(), StrError> { + /// // data + /// let (xa, xb) = (-4.0, 4.0); + /// let nn = 2; + /// let zz = InterpChebyshev::points(nn); + /// let dx = xb - xa; + /// let np = nn + 1; + /// let mut uu = Vector::new(np); + /// for i in 0..np { + /// let x = (xb + xa + dx * zz[i]) / 2.0; + /// uu[i] = x * x - 1.0; + /// } + /// + /// // interpolant + /// let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + /// interp.set_data(uu.as_data())?; + /// + /// // check + /// approx_eq(interp.eval(0.0)?, -1.0, 1e-15); + /// Ok(()) + /// } + /// ``` + pub fn set_data(&mut self, uu: &[f64]) -> Result<(), StrError> { let np = uu.len(); - if np == 0 { - return Err("the number of points = uu.len() must be ≥ 1"); + if np < 1 { + return Err("the number of points (uu.len()) must be ≥ 1"); } let nn = np - 1; - let mut interp = InterpChebyshev { - nn, - np, - xa, - xb, - dx: xb - xa, - a: vec![0.0; np], - uu_rev: vec![0.0; np], - constant_fx: 0.0, - ready: true, - }; - for i in 0..np { - interp.uu_rev[nn - i] = uu[i]; + if nn > self.nn_max { + return Err("nn (=uu.len()-1) must be ≤ nn_max"); } - if nn == 0 { - interp.constant_fx = uu[0]; + self.nn = nn; + if self.nn == 0 { + self.constant_fx = uu[0]; } else { - chebyshev_coefficients(&mut interp.a, &interp.uu_rev, nn); + for i in 0..np { + self.uu_rev[nn - i] = uu[i]; + } + chebyshev_coefficients(&mut self.a, &self.uu_rev, self.nn); } - Ok(interp) + self.ready = true; + Ok(()) } - /// Allocates a new instance using adaptive interpolation + /// Performs adaptive interpolation for given function /// /// # Input /// - /// * `nn_max` -- maximum polynomial degree N (≤ 2048) /// * `tol` -- tolerance to truncate the Chebyshev series (e.g., 1e-8) - /// * `xa` -- lower bound - /// * `xb` -- upper bound (> xa + ϵ) /// * `args` -- extra arguments for the f(x) function - /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + /// * `f` -- callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. /// /// # Method /// @@ -297,65 +262,46 @@ impl InterpChebyshev { /// let (xa, xb) = (-1.0, 1.0); /// /// // interpolant - /// let nn_max = 200; + /// let nn_max = 10; /// let tol = 1e-8; /// let args = &mut 0; - /// let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f)?; + /// let mut interp = InterpChebyshev::new(nn_max, xa, xb)?; + /// interp.adapt_function(tol, args, f)?; /// /// // check /// assert_eq!(interp.get_degree(), 2); /// Ok(()) /// } /// ``` - pub fn new_adapt_f( - nn_max: usize, - tol: f64, - xa: f64, - xb: f64, - args: &mut A, - mut f: F, - ) -> Result + pub fn adapt_function(&mut self, tol: f64, args: &mut A, mut f: F) -> Result<(), StrError> where F: FnMut(f64, &mut A) -> Result, { - if nn_max > 2048 { - return Err("the maximum degree N must be ≤ 2048"); - } - if xb <= xa + TOL_RANGE { - return Err("xb must be greater than xa + ϵ"); - } - let np_max = nn_max + 1; - let mut work_a = vec![0.0; np_max]; - let mut work_uu_rev = vec![0.0; np_max]; let mut an_prev = 0.0; - for nn in 1..=nn_max { - chebyshev_data_vector(&mut work_uu_rev, nn, xa, xb, args, &mut f)?; - chebyshev_coefficients(&mut work_a, &work_uu_rev, nn); - let an = work_a[nn]; + for nn in 1..=self.nn_max { + chebyshev_data_vector(&mut self.uu_rev, nn, self.xa, self.xb, args, &mut f)?; + chebyshev_coefficients(&mut self.a, &self.uu_rev, nn); + let an = self.a[nn]; if nn > 1 && f64::abs(an_prev) < tol && f64::abs(an) < tol { - let nn_final = nn - 2; // -2 because the last two coefficients are zero - return Ok(InterpChebyshev::new_with_f(nn_final, xa, xb, args, f)?); + let actual_nn = nn - 2; // -2 because the last two coefficients are zero + self.set_function(actual_nn, args, f).unwrap(); + return Ok(()); } an_prev = an; } Err("adaptive interpolation did not converge") } - /// Allocates a new instance using adaptive interpolation on the data vector U + /// Performs adaptive interpolation for given data /// /// # Input /// - /// * `nn_max` -- maximum polynomial degree N (≤ 2048) /// * `tol` -- tolerance to truncate the Chebyshev series (e.g., 1e-8) - /// * `xa` -- lower bound - /// * `xb` -- upper bound (> xa + ϵ) - /// * `uu` -- the data vector such that `Uᵢ = f(xᵢ)`; i.e., the function evaluated at the - /// Chebyshev-Gauss-Lobatto coordinates (from -1 to 1). These coordinates - /// are available via the [InterpChebyshev::points()] function. + /// * `uu` -- U vector with the data values /// /// # Method /// - /// See [InterpChebyshev::new_adapt_f()] + /// See [InterpChebyshev::adapt_function()] /// /// # Examples /// @@ -364,21 +310,35 @@ impl InterpChebyshev { /// /// fn main() -> Result<(), StrError> { /// // data - /// let uu = [-7.0, -4.0, 0.5, 3.0]; + /// let uu = [-7.0, -4.5, 0.5, 3.0]; /// let (xa, xb) = (0.0, 1.0); /// /// // interpolant - /// let nn_max = 100; + /// let nn_max = 10; /// let tol = 1e-8; - /// let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, &uu)?; - /// let nn = interp.get_degree(); + /// let mut interp = InterpChebyshev::new(nn_max, xa, xb)?; + /// interp.adapt_data(tol, &uu)?; + /// + /// // check + /// assert_eq!(interp.get_degree(), 1); /// Ok(()) /// } /// ``` - pub fn new_adapt_uu(nn_max: usize, tol: f64, xa: f64, xb: f64, uu: &[f64]) -> Result { - let fit = InterpChebyshev::new_with_uu(xa, xb, uu)?; + pub fn adapt_data(&mut self, tol: f64, uu: &[f64]) -> Result<(), StrError> { + // fit data + let np = uu.len(); + if np < 1 { + return Err("the number of points (uu.len()) must be ≥ 1"); + } + let nn = np - 1; + if nn > self.nn_max { + return Err("nn must be ≤ nn_max"); + } + let mut fit = InterpChebyshev::new(nn, self.xa, self.xb).unwrap(); + fit.set_data(uu).unwrap(); + // adapt polynomial let args = &mut 0; - InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, |x, _: &mut NoArgs| fit.eval(x)) + self.adapt_function(tol, args, |x, _: &mut NoArgs| fit.eval(x)) } /// Evaluates the interpolated f(x) function @@ -392,7 +352,7 @@ impl InterpChebyshev { /// Mathematics of Computation, 9:118-120 pub fn eval(&self, x: f64) -> Result { if !self.ready { - return Err("all U components must be set first"); + return Err("the data or function must be set first"); } if self.nn == 0 { return Ok(self.constant_fx); @@ -402,7 +362,8 @@ impl InterpChebyshev { let mut b_k = 0.0; let mut b_k_plus_1 = 0.0; let mut b_k_plus_2: f64; - for k in (1..self.np).rev() { + let np = self.nn + 1; + for k in (1..np).rev() { b_k_plus_2 = b_k_plus_1; b_k_plus_1 = b_k; b_k = z2 * b_k_plus_1 - b_k_plus_2 + self.a[k]; @@ -414,14 +375,15 @@ impl InterpChebyshev { /// Evaluates the interpolated f(x) function using the slower trigonometric functions pub fn eval_using_trig(&self, x: f64) -> Result { if !self.ready { - return Err("all U components must be set first"); + return Err("the data or function must be set first"); } if self.nn == 0 { return Ok(self.constant_fx); } let z = f64::max(-1.0, f64::min(1.0, (2.0 * x - self.xb - self.xa) / self.dx)); let mut sum = 0.0; - for k in 0..self.np { + let np = self.nn + 1; + for k in 0..np { sum += self.a[k] * chebyshev_tn(k, z); } Ok(sum) @@ -495,7 +457,7 @@ where assert!(xb > xa); assert!(work_uu_rev.len() >= np); - // reverse data vector U (associated to Chebyshev-Gauss-Lobatto points from 1 to -1) + // reversed data vector U (associated to Chebyshev-Gauss-Lobatto points from 1 to -1) let nf = nn as f64; let uu_rev = &mut work_uu_rev[0..np]; for k in 0..np { @@ -536,11 +498,16 @@ fn chebyshev_coefficients(work_a: &mut [f64], work_uu_rev: &[f64], nn: usize) { mod tests { use super::{chebyshev_coefficients, InterpChebyshev}; use crate::math::PI; - use crate::{approx_eq, array_approx_eq, NoArgs, Vector, TOL_RANGE}; + use crate::{approx_eq, array_approx_eq, NoArgs, Vector}; #[allow(unused)] use plotpy::{Curve, Legend, Plot}; + #[test] + fn points_works() { + assert_eq!(InterpChebyshev::points(2).as_data(), &[-1.0, 0.0, 1.0]); + } + #[test] fn new_captures_errors() { assert_eq!( @@ -551,79 +518,80 @@ mod tests { #[test] fn new_works() { - let nn = 2; + let nn_max = 2; let (xa, xb) = (-4.0, 4.0); - let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - let np = nn + 1; + let interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + let np_max = nn_max + 1; + assert_eq!(interp.nn_max, nn_max); + assert_eq!(interp.nn, 0); assert_eq!(interp.xa, xa); assert_eq!(interp.xb, xb); assert_eq!(interp.dx, 8.0); - assert_eq!(interp.nn, nn); - assert_eq!(interp.np, np); - assert_eq!(interp.a.len(), np); - assert_eq!(interp.uu_rev.len(), np); + assert_eq!(interp.a.len(), np_max); + assert_eq!(interp.uu_rev.len(), np_max); assert_eq!(interp.constant_fx, 0.0); assert_eq!(interp.ready, false); } #[test] - fn new_with_f_captures_errors() { + fn set_function_captures_errors() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; _ = f(0.0, args); // for coverage tool - assert_eq!( - InterpChebyshev::new_with_f(2, 0.0, 0.0, args, f).err(), - Some("xb must be greater than xa + ϵ") - ); + let mut interp = InterpChebyshev::new(2, 0.0, 1.0).unwrap(); + assert_eq!(interp.set_function(3, args, f).err(), Some("nn must be ≤ nn_max")); let f = |_: f64, _: &mut NoArgs| Err("stop"); - assert_eq!(InterpChebyshev::new_with_f(0, 0.0, 1.0, args, f).err(), Some("stop")); - assert_eq!(InterpChebyshev::new_with_f(1, 0.0, 1.0, args, f).err(), Some("stop")); + assert_eq!(interp.set_function(0, args, f).err(), Some("stop")); + assert_eq!(interp.set_function(1, args, f).err(), Some("stop")); } #[test] - fn new_with_f_and_estimate_max_error_work() { + fn set_function_and_estimate_max_error_work() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let (xa, xb) = (-4.0, 4.0); let args = &mut 0; + let nn_max = 2; // N = 2 - let nn = 2; - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.set_function(2, args, f).unwrap(); let err = interp.estimate_max_error(100, args, f).unwrap(); println!("N = 2, err = {:e}", err); assert!(err < 1e-14); // N = 1 - let nn = 1; - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + interp.set_function(1, args, f).unwrap(); let err = interp.estimate_max_error(100, args, f).unwrap(); - println!("N = 1, err = {:e}", err); + println!("N = 1, err = {}", err); assert!(err > 15.0); } #[test] - fn new_with_uu_captures_errors() { + fn set_data_captures_errors() { + let mut interp = InterpChebyshev::new(1, 0.0, 1.0).unwrap(); let uu = Vector::new(0); assert_eq!( - InterpChebyshev::new_with_uu(0.0, 0.0, uu.as_data()).err(), - Some("xb must be greater than xa + ϵ") + interp.set_data(uu.as_data()).err(), + Some("the number of points (uu.len()) must be ≥ 1") ); + let uu = Vector::new(3); assert_eq!( - InterpChebyshev::new_with_uu(0.0, 1.0, uu.as_data()).err(), - Some("the number of points = uu.len() must be ≥ 1") + interp.set_data(uu.as_data()).err(), + Some("nn (=uu.len()-1) must be ≤ nn_max") ); } #[test] - fn new_with_uu_works_n0() { + fn set_data_works_constant_fx() { + let nn_max = 3; let (xa, xb) = (0.0, 1.0); let uu = &[1.0]; - let interp = InterpChebyshev::new_with_uu(xa, xb, uu).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.set_data(uu).unwrap(); assert_eq!(interp.eval(3.0).unwrap(), 1.0); } #[test] - fn new_with_uu_works() { + fn set_data_works() { let f = |x, _: &mut NoArgs| Ok(-1.0 + f64::sqrt(1.0 + 2.0 * x * 1200.0)); - let (xa, xb) = (0.0, 1.0); let nn = 6; let zz = InterpChebyshev::points(nn); @@ -633,7 +601,8 @@ mod tests { let x = (xb + xa + dx * z) / 2.0; f(x, args).unwrap() }); - let interp = InterpChebyshev::new_with_uu(xa, xb, uu.as_data()).unwrap(); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_data(uu.as_data()).unwrap(); // check let np = nn + 1; @@ -671,72 +640,48 @@ mod tests { .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") - .save("/tmp/russell_lab/test_new_with_uu.svg") + .save("/tmp/russell_lab/test_interp_chebyshev_set_data.svg") .unwrap(); */ } #[test] fn eval_captures_errors() { - let nn = 2; + let nn_max = 2; let (xa, xb) = (-4.0, 4.0); - let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - assert_eq!(interp.eval(0.0).err(), Some("all U components must be set first")); + let interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + assert_eq!(interp.eval(0.0).err(), Some("the data or function must be set first")); } #[test] fn eval_using_trig_captures_errors() { - let nn = 2; + let nn_max = 2; let (xa, xb) = (-4.0, 4.0); - let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + let interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); assert_eq!( interp.eval_using_trig(0.0).err(), - Some("all U components must be set first") + Some("the data or function must be set first") ); } #[test] - fn new_adapt_f_captures_errors() { - struct Args { - count: usize, - } - let f = |x: f64, a: &mut Args| { - a.count += 1; - if a.count == 3 { - return Err("stop with count = 3"); - } - if a.count == 18 { - return Err("stop with count = 18"); - } - Ok(x * x - 1.0) - }; - let mut args = Args { count: 0 }; + fn adapt_function_captures_errors() { + let f = |_: f64, _: &mut NoArgs| Err("stop"); let (xa, xb) = (-4.0, 4.0); let tol = 1e-3; + let nn_max = 2; + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + let args = &mut 0; + assert_eq!(interp.adapt_function(tol, args, f).err(), Some("stop")); + interp.nn_max = 0; // WRONG assert_eq!( - InterpChebyshev::new_adapt_f(2049, tol, xa, xb, &mut args, f).err(), - Some("the maximum degree N must be ≤ 2048") - ); - assert_eq!( - InterpChebyshev::new_adapt_f(2, tol, xa, xa + TOL_RANGE, &mut args, f).err(), - Some("xb must be greater than xa + ϵ") - ); - assert_eq!( - InterpChebyshev::new_adapt_f(1, tol, xa, xb, &mut args, f).err(), + interp.adapt_function(tol, args, f).err(), Some("adaptive interpolation did not converge") ); - assert_eq!( - InterpChebyshev::new_adapt_f(2, tol, xa, xb, &mut args, f).err(), - Some("stop with count = 3") - ); - assert_eq!( - InterpChebyshev::new_adapt_f(4, tol, xa, xb, &mut args, f).err(), - Some("stop with count = 18") - ); } #[test] - fn new_adapt_f_and_eval_work() { + fn adapt_function_and_eval_work() { let functions = [ |_: f64, _: &mut NoArgs| Ok(2.0), // 0 |x: f64, _: &mut NoArgs| Ok(x - 0.5), // 1 @@ -759,6 +704,17 @@ mod tests { (-2.34567, 12.34567), // 7 (-0.995 * PI, 0.995 * PI), // 8 ]; + let degrees_answer = [ + 0, // 0 + 1, // 1 + 2, // 2 + 3, // 3 + 4, // 4 + 5, // 5 + 60, // 6 + 173, // 7 + 126, // 8 + ]; let tols_adapt = [ 0.0, // 0 0.0, // 1 @@ -787,8 +743,10 @@ mod tests { for (index, f) in functions.into_iter().enumerate() { // adaptive interpolation let (xa, xb) = ranges[index]; - let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_function(tol, args, f).unwrap(); let nn = interp.get_degree(); + assert_eq!(nn, degrees_answer[index]); // check adaptive interpolation let err = interp.estimate_max_error(1000, args, f).unwrap(); @@ -830,7 +788,7 @@ mod tests { .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") .save(&format!( - "/tmp/russell_lab/test_interp_chebyshev_new_adapt_f_{:0>3}.svg", + "/tmp/russell_lab/test_interp_chebyshev_adapt_function_{:0>3}.svg", index )) .unwrap(); @@ -839,18 +797,17 @@ mod tests { } #[test] - fn new_adapt_uu_captures_errors() { + fn adapt_data_captures_errors() { let uu = []; - let nn_max = 4; - let tol = 1e-7; + let mut interp = InterpChebyshev::new(3, 0.0, 1.0).unwrap(); assert_eq!( - InterpChebyshev::new_adapt_uu(nn_max, tol, 0.0, 1.0, &uu).err(), - Some("the number of points = uu.len() must be ≥ 1") + interp.adapt_data(1e-7, &uu).err(), + Some("the number of points (uu.len()) must be ≥ 1") ); } #[test] - fn new_adapt_uu_works() { + fn adapt_data_works() { let data_generators = [ |_: f64| 2.0, // 0 |x: f64| x - 0.5, // 1 @@ -913,7 +870,8 @@ mod tests { } // adaptive interpolation - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_data(tol, uu.as_data()).unwrap(); let nn = interp.get_degree(); // check adapted degrees @@ -959,7 +917,7 @@ mod tests { .grid_and_labels("x", "f(x)") .set_figure_size_points(fig_width, 500.0) .save(&format!( - "/tmp/russell_lab/test_interp_chebyshev_new_adapt_uu_{:0>3}.svg", + "/tmp/russell_lab/test_interp_chebyshev_adapt_data_{:0>3}.svg", index )) .unwrap(); @@ -981,15 +939,15 @@ mod tests { |x: f64| f64::ln(2.0 * f64::cos(x / 2.0)), // 8 ]; let degrees_fit = [ - 10, // 0 - 10, // 1 - 10, // 2 - 10, // 3 - 10, // 4 - 10, // 5 - 30, // 6 - 1000, // 7 - 10, // 8 + 10, // 0 + 10, // 1 + 10, // 2 + 10, // 3 + 10, // 4 + 10, // 5 + 30, // 6 + 400, // 7 + 10, // 8 ]; let degrees_answer = [ 2, // 0 @@ -1033,7 +991,8 @@ mod tests { } // adaptive interpolation - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_data(tol, uu.as_data()).unwrap(); let nn = interp.get_degree(); // check adapted degrees @@ -1078,7 +1037,7 @@ mod tests { .grid_and_labels("x", "f(x)") .set_figure_size_points(fig_width, 500.0) .save(&format!( - "/tmp/russell_lab/test_interp_chebyshev_new_adapt_noisy_uu_{:0>3}.svg", + "/tmp/russell_lab/test_interp_chebyshev_adapt_data_noisy_{:0>3}.svg", index )) .unwrap(); @@ -1091,12 +1050,12 @@ mod tests { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; _ = f(0.0, args); // for coverage tool - let nn = 2; + let nn_max = 2; let (xa, xb) = (-4.0, 4.0); - let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + let interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); assert_eq!( interp.estimate_max_error(100, args, f).err(), - Some("all U components must be set first") + Some("the data or function must be set first") ); } @@ -1105,37 +1064,19 @@ mod tests { let f = |_: f64, _: &mut NoArgs| Err("stop"); let args = &mut 0; let (xa, xb) = (0.0, 1.0); - let uu = &[1.0]; - let interp = InterpChebyshev::new_with_uu(xa, xb, uu).unwrap(); + let mut interp = InterpChebyshev::new(2, xa, xb).unwrap(); + interp.set_data(&[0.0]).unwrap(); assert_eq!(interp.estimate_max_error(2, args, f).err(), Some("stop")); } - #[test] - fn set_uu_value_works() { - let nn = 2; - let (xa, xb) = (-4.0, 4.0); - let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - let zz = InterpChebyshev::points(nn); - let dx = xb - xa; - let np = nn + 1; - for i in 0..np { - let x = (xb + xa + dx * zz[i]) / 2.0; - interp.set_uu_value(i, x * x - 1.0); - } - // check - let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); - let args = &mut 0; - let err = interp.estimate_max_error(100, args, f).unwrap(); - assert!(err < 1e-14); - } - #[test] fn getters_work() { let f = |x: f64, _: &mut NoArgs| Ok(x * x - 1.0); let (xa, xb) = (-4.0, 4.0); - let nn = 2; + let nn_max = 2; + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); let args = &mut 0; - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + interp.set_function(2, args, f).unwrap(); assert_eq!(interp.get_degree(), 2); assert_eq!(interp.get_range(), (-4.0, 4.0, 8.0)); assert_eq!(interp.is_ready(), true); diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index 9fd07d78..f499d9de 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -147,7 +147,8 @@ impl MultiRootSolverCheby { /// /// // interpolant /// let nn = 2; - /// let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f)?; + /// let mut interp = InterpChebyshev::new(nn, xa, xb)?; + /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval /// let mut solver = MultiRootSolverCheby::new(nn)?; @@ -232,7 +233,8 @@ impl MultiRootSolverCheby { /// /// // interpolant /// let nn = 2; - /// let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f)?; + /// let mut interp = InterpChebyshev::new(nn, xa, xb)?; + /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval /// let mut solver = MultiRootSolverCheby::new(nn)?; @@ -420,11 +422,6 @@ mod tests { solver.find(&interp).err(), Some("the interpolant must have the same degree N as the solver") ); - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - assert_eq!( - solver.find(&interp).err(), - Some("the interpolant must have the U vector already computed") - ); } #[test] @@ -433,7 +430,8 @@ mod tests { let (xa, xb) = (-4.0, 4.0); let nn = 3; let args = &mut 0; - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); let mut solver = MultiRootSolverCheby::new(nn).unwrap(); assert_eq!( solver.find(&interp).err(), @@ -450,7 +448,8 @@ mod tests { // interpolant let nn = 2; let args = &mut 0; - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); // find roots let mut solver = MultiRootSolverCheby::new(nn).unwrap(); @@ -490,7 +489,8 @@ mod tests { // interpolant let nn = 2; let args = &mut 0; - let interp = InterpChebyshev::new_with_f(nn, xa, xb, args, f).unwrap(); + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); // find roots let mut solver = MultiRootSolverCheby::new(nn).unwrap(); @@ -532,7 +532,8 @@ mod tests { println!("\n==================================================================="); println!("\n{}", test.name); let (xa, xb) = test.range; - let interp = InterpChebyshev::new_adapt_f(nn_max, tol, xa, xb, args, test.f).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_function(tol, args, test.f).unwrap(); let nn = interp.get_degree(); let mut solver = MultiRootSolverCheby::new(nn).unwrap(); let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); @@ -621,7 +622,8 @@ mod tests { // interpolant let nn_max = 100; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_data(tol, uu.as_data()).unwrap(); let nn = interp.get_degree(); // find all roots in the interval @@ -648,7 +650,8 @@ mod tests { // interpolant let nn_max = 100; let tol = 1e-8; - let interp = InterpChebyshev::new_adapt_uu(nn_max, tol, xa, xb, uu.as_data()).unwrap(); + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_data(tol, uu.as_data()).unwrap(); let nn = interp.get_degree(); // find all roots in the interval diff --git a/russell_lab/zscripts/run-examples.bash b/russell_lab/zscripts/run-examples.bash index 2bba7c66..69f34d51 100755 --- a/russell_lab/zscripts/run-examples.bash +++ b/russell_lab/zscripts/run-examples.bash @@ -3,5 +3,5 @@ for example in examples/*.rs; do filename="$(basename "$example")" filekey="${filename%%.*}" - cargo run --example $filekey + cargo run --example $filekey --features intel_mkl done From 6ea38b158705f7a189e32e2edba145ccf00618de Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 15:50:52 +1000 Subject: [PATCH 71/93] Improve test --- russell_lab/src/algo/interp_chebyshev.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index dde8ed39..4b1725a6 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -799,11 +799,13 @@ mod tests { #[test] fn adapt_data_captures_errors() { let uu = []; - let mut interp = InterpChebyshev::new(3, 0.0, 1.0).unwrap(); + let mut interp = InterpChebyshev::new(1, 0.0, 1.0).unwrap(); assert_eq!( interp.adapt_data(1e-7, &uu).err(), Some("the number of points (uu.len()) must be ≥ 1") ); + let uu = [1.0, 2.0, 3.0]; + assert_eq!(interp.adapt_data(1e-7, &uu).err(), Some("nn must be ≤ nn_max")); } #[test] From 5d6371bd8c2744ad496e0ca09b5bb71b7f62b81e Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 17:40:15 +1000 Subject: [PATCH 72/93] Simplify MultiRootSolverChevy --- .../examples/algo_multi_root_solver_cheby.rs | 6 +- .../src/algo/multi_root_solver_cheby.rs | 290 +++++++----------- 2 files changed, 119 insertions(+), 177 deletions(-) diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 4ef1fa74..fc4e1ef0 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -18,15 +18,15 @@ fn main() -> Result<(), StrError> { println!("N = {}", nn); // find all roots in the interval - let mut solver = MultiRootSolverCheby::new(nn)?; + let solver = MultiRootSolverCheby::new(); let roots = Vector::from(&solver.find(&interp)?); let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); println!("roots =\n{}", roots); println!("f @ roots =\n{}", print_vec_exp(&f_at_roots)); // polish the roots - let mut roots_polished = Vector::new(roots.dim()); - solver.polish_roots_newton(roots_polished.as_mut_data(), roots.as_data(), xa, xb, args, f)?; + let mut roots_polished = roots.clone(); + solver.polish_roots_newton(roots_polished.as_mut_data(), xa, xb, args, f)?; let f_at_roots_polished = roots_polished.get_mapped(|x| f(x, args).unwrap()); println!("polished roots =\n{}", roots_polished); println!("f @ polished roots =\n{}", print_vec_exp(&f_at_roots_polished)); diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/multi_root_solver_cheby.rs index f499d9de..3656194e 100644 --- a/russell_lab/src/algo/multi_root_solver_cheby.rs +++ b/russell_lab/src/algo/multi_root_solver_cheby.rs @@ -58,21 +58,6 @@ pub struct MultiRootSolverCheby { /// Default = 15 pub newton_max_iterations: usize, - /// Holds the polynomial degree N - nn: usize, - - /// Holds the companion matrix A - aa: Matrix, - - /// Holds the real part of the eigenvalues - l_real: Vector, - - /// Holds the imaginary part of the eigenvalues - l_imag: Vector, - - /// Holds all possible roots (dim == N) - roots: Vector, - /// Stepsize for one-sided differences h_osd: f64, @@ -82,42 +67,17 @@ pub struct MultiRootSolverCheby { impl MultiRootSolverCheby { /// Allocates a new instance - /// - /// # Input - /// - /// * `nn` -- polynomial degree N (must be ≥ 1) - pub fn new(nn: usize) -> Result { - // check - if nn < 1 { - return Err("the degree N must be ≥ 1"); - } - - // companion matrix (except last row) - let mut aa = Matrix::new(nn, nn); - if nn > 1 { - aa.set(0, 1, 1.0); - for r in 1..(nn - 1) { - aa.set(r, r + 1, 0.5); // upper diagonal - aa.set(r, r - 1, 0.5); // lower diagonal - } - } - - // done - Ok(MultiRootSolverCheby { + pub fn new() -> Self { + MultiRootSolverCheby { tol_zero_an: 1e-13, tol_rel_imag: 1.0e-8, tol_abs_boundary: TOL_RANGE / 10.0, newton_tol_zero_dx: 1e-13, newton_tol_zero_fx: 1e-13, newton_max_iterations: 15, - nn, - aa, - l_real: Vector::new(nn), - l_imag: Vector::new(nn), - roots: Vector::new(nn), h_osd: f64::powf(f64::EPSILON, 1.0 / 2.0), h_cen: f64::powf(f64::EPSILON, 1.0 / 3.0), - }) + } } /// Find all roots in the interval @@ -151,71 +111,76 @@ impl MultiRootSolverCheby { /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval - /// let mut solver = MultiRootSolverCheby::new(nn)?; - /// let roots = Vector::from(&solver.find(&interp)?); - /// vec_approx_eq(&roots, &[-1.0, 1.0], 1e-15); + /// let mut solver = MultiRootSolverCheby::new(); + /// let roots = solver.find(&interp)?; + /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); /// Ok(()) /// } /// ``` - pub fn find(&mut self, interp: &InterpChebyshev) -> Result<&[f64], StrError> { + pub fn find(&self, interp: &InterpChebyshev) -> Result, StrError> { // check - let nn = interp.get_degree(); - if nn != self.nn { - return Err("the interpolant must have the same degree N as the solver"); - } if !interp.is_ready() { - return Err("the interpolant must have the U vector already computed"); + return Err("the interpolant must initialized first"); + } + + // handle constant function + let nn = interp.get_degree(); + if nn == 0 { + return Ok(Vec::new()); } - // last expansion coefficient + // expansion coefficients let a = interp.get_coefficients(); let an = a[nn]; if f64::abs(an) < self.tol_zero_an { return Err("the trailing Chebyshev coefficient vanishes; try a smaller degree N"); } - // linear function + // handle linear function let (xa, xb, dx) = interp.get_range(); if nn == 1 { let z = -a[0] / a[1]; - let nr = if f64::abs(z) <= 1.0 + self.tol_abs_boundary { - self.roots[0] = (xb + xa + dx * z) / 2.0; - 1 + if f64::abs(z) <= 1.0 + self.tol_abs_boundary { + let root = (xb + xa + dx * z) / 2.0; + return Ok(vec![root]); } else { - 0 - }; - return Ok(&self.roots.as_data()[..nr]); + return Ok(Vec::new()); + } } - // last row of the companion matrix + // companion matrix + let mut aa = Matrix::new(nn, nn); + aa.set(0, 1, 1.0); + for r in 1..(nn - 1) { + aa.set(r, r + 1, 0.5); // upper diagonal + aa.set(r, r - 1, 0.5); // lower diagonal + } for k in 0..nn { - self.aa.set(nn - 1, k, -0.5 * a[k] / an); + aa.set(nn - 1, k, -0.5 * a[k] / an); } - self.aa.add(nn - 1, nn - 2, 0.5); + aa.add(nn - 1, nn - 2, 0.5); // eigenvalues - mat_eigenvalues(&mut self.l_real, &mut self.l_imag, &mut self.aa).unwrap(); + let mut l_real = Vector::new(nn); + let mut l_imag = Vector::new(nn); + mat_eigenvalues(&mut l_real, &mut l_imag, &mut aa).unwrap(); // roots = real eigenvalues within the interval - let mut nroot = 0; + let mut roots = Vec::new(); for i in 0..nn { - if f64::abs(self.l_imag[i]) < self.tol_rel_imag * f64::abs(self.l_real[i]) { - if f64::abs(self.l_real[i]) <= 1.0 + self.tol_abs_boundary { - let x = (xb + xa + dx * self.l_real[i]) / 2.0; - self.roots[nroot] = f64::max(xa, f64::min(xb, x)); - nroot += 1; + if f64::abs(l_imag[i]) < self.tol_rel_imag * f64::abs(l_real[i]) { + if f64::abs(l_real[i]) <= 1.0 + self.tol_abs_boundary { + let x = (xb + xa + dx * l_real[i]) / 2.0; + roots.push(f64::max(xa, f64::min(xb, x))); } } } // sort roots - for i in nroot..nn { - self.roots[i] = f64::MAX; + if roots.len() > 0 { + roots.sort_by(|a, b| a.partial_cmp(b).unwrap()); } - self.roots.as_mut_data().sort_by(|a, b| a.partial_cmp(b).unwrap()); - - // results - Ok(&self.roots.as_data()[..nroot]) + Ok(roots) } /// Polishes the roots using Newton's method @@ -237,21 +202,19 @@ impl MultiRootSolverCheby { /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval - /// let mut solver = MultiRootSolverCheby::new(nn)?; - /// let roots = Vector::from(&solver.find(&interp)?); - /// vec_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate + /// let mut solver = MultiRootSolverCheby::new(); + /// let mut roots = solver.find(&interp)?; + /// array_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate /// /// // polish the roots - /// let mut roots_polished = Vector::new(roots.dim()); - /// solver.polish_roots_newton(roots_polished.as_mut_data(), roots.as_data(), xa, xb, args, f)?; - /// vec_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-15); // accurate + /// solver.polish_roots_newton(&mut roots, xa, xb, args, f)?; + /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); // accurate /// Ok(()) /// } - ///``` + /// ``` pub fn polish_roots_newton( &self, - roots_out: &mut [f64], - roots_in: &[f64], + roots: &mut [f64], xa: f64, xb: f64, args: &mut A, @@ -261,18 +224,15 @@ impl MultiRootSolverCheby { F: FnMut(f64, &mut A) -> Result, { // check - let nr = roots_in.len(); + let nr = roots.len(); if nr < 1 { return Err("at least one root is required"); } - if roots_out.len() != roots_in.len() { - return Err("root_in and root_out must have the same lengths"); - } // Newton's method with approximate Jacobian let h_cen_2 = self.h_cen * 2.0; for r in 0..nr { - let mut x = roots_in[r]; + let mut x = roots[r]; let mut converged = false; for _ in 0..self.newton_max_iterations { // check convergence on f(x) @@ -311,7 +271,7 @@ impl MultiRootSolverCheby { if !converged { return Err("Newton's method did not converge"); } - roots_out[r] = x; + roots[r] = x; } Ok(()) } @@ -325,7 +285,6 @@ mod tests { use crate::algo::NoArgs; use crate::InterpChebyshev; use crate::{approx_eq, array_approx_eq, get_test_functions}; - use crate::{mat_approx_eq, Matrix}; #[allow(unused)] use crate::{StrError, Vector}; @@ -397,30 +356,15 @@ mod tests { } */ - #[test] - fn new_captures_errors() { - let nn = 0; - assert_eq!(MultiRootSolverCheby::new(nn).err(), Some("the degree N must be ≥ 1")); - } - - #[test] - fn new_works() { - let nn = 2; - let solver = MultiRootSolverCheby::new(nn).unwrap(); - let aa_correct = Matrix::from(&[[0.0, 1.0000], [0.0, 0.0]]); - mat_approx_eq(&solver.aa, &aa_correct, 1e-15); - } - #[test] fn find_captures_errors() { let (xa, xb) = (-4.0, 4.0); let nn = 2; let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - let nn_wrong = 3; - let mut solver = MultiRootSolverCheby::new(nn_wrong).unwrap(); + let solver = MultiRootSolverCheby::new(); assert_eq!( solver.find(&interp).err(), - Some("the interpolant must have the same degree N as the solver") + Some("the interpolant must initialized first") ); } @@ -432,7 +376,7 @@ mod tests { let args = &mut 0; let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); interp.set_function(nn, args, f).unwrap(); - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); + let solver = MultiRootSolverCheby::new(); assert_eq!( solver.find(&interp).err(), Some("the trailing Chebyshev coefficient vanishes; try a smaller degree N") @@ -452,11 +396,11 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); - let mut roots_polished = vec![0.0; roots_unpolished.len()]; + let solver = MultiRootSolverCheby::new(); + let roots_unpolished = solver.find(&interp).unwrap(); + let mut roots_polished = roots_unpolished.clone(); solver - .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, f) + .polish_roots_newton(&mut roots_polished, xa, xb, args, f) .unwrap(); println!("n_roots = {}", roots_polished.len()); println!("roots_unpolished = {:?}", roots_unpolished); @@ -480,6 +424,26 @@ mod tests { array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); } + #[test] + fn polish_roots_newton_captures_errors() { + let f = |_, _: &mut NoArgs| Ok(0.0); + let args = &mut 0; + let _ = f(0.0, args); + let (xa, xb) = (-1.0, 1.0); + let mut solver = MultiRootSolverCheby::new(); + let mut roots = Vec::new(); + assert_eq!( + solver.polish_roots_newton(&mut roots, xa, xb, args, f).err(), + Some("at least one root is required") + ); + let mut roots = [0.0]; + solver.newton_max_iterations = 0; + assert_eq!( + solver.polish_roots_newton(&mut roots, xa, xb, args, f).err(), + Some("Newton's method did not converge") + ); + } + #[test] fn polish_roots_newton_works() { // function @@ -493,11 +457,11 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); - let mut roots_polished = vec![0.0; roots_unpolished.len()]; + let solver = MultiRootSolverCheby::new(); + let roots_unpolished = solver.find(&interp).unwrap(); + let mut roots_polished = roots_unpolished.clone(); solver - .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, f) + .polish_roots_newton(&mut roots_polished, xa, xb, args, f) .unwrap(); println!("n_roots = {}", roots_polished.len()); println!("roots_unpolished = {:?}", roots_unpolished); @@ -534,12 +498,11 @@ mod tests { let (xa, xb) = test.range; let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); interp.adapt_function(tol, args, test.f).unwrap(); - let nn = interp.get_degree(); - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots_unpolished = Vec::from(solver.find(&interp).unwrap()); - let mut roots_polished = vec![0.0; roots_unpolished.len()]; + let solver = MultiRootSolverCheby::new(); + let roots_unpolished = solver.find(&interp).unwrap(); + let mut roots_polished = roots_unpolished.clone(); solver - .polish_roots_newton(&mut roots_polished, &roots_unpolished, xa, xb, args, test.f) + .polish_roots_newton(&mut roots_polished, xa, xb, args, test.f) .unwrap(); for xr in &roots_polished { let fx = (test.f)(*xr, args).unwrap(); @@ -564,8 +527,8 @@ mod tests { if *id == 9 { assert_eq!(roots_unpolished.len(), 93); } - /* // figure + /* let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; graph( &format!("test_multi_root_solver_cheby_{:0>3}", id), @@ -582,54 +545,39 @@ mod tests { } #[test] - fn polish_roots_newton_captures_errors() { - let f = |_, _: &mut NoArgs| Ok(0.0); - let args = &mut 0; - let _ = f(0.0, args); - let (xa, xb) = (-1.0, 1.0); - let mut solver = MultiRootSolverCheby::new(2).unwrap(); - let roots_in = Vec::new(); - let mut roots_out = [0.0]; - assert_eq!( - solver - .polish_roots_newton(&mut roots_out, &roots_in, xa, xb, args, f) - .err(), - Some("at least one root is required") - ); - let roots_in = [0.0, 1.0]; - assert_eq!( - solver - .polish_roots_newton(&mut roots_out, &roots_in, xa, xb, args, f) - .err(), - Some("root_in and root_out must have the same lengths") - ); - let roots_in = [0.0]; - solver.newton_max_iterations = 0; - assert_eq!( - solver - .polish_roots_newton(&mut roots_out, &roots_in, xa, xb, args, f) - .err(), - Some("Newton's method did not converge") - ); + fn constant_function_works() { + // data + let (xa, xb) = (0.0, 1.0); + let uu = &[0.5]; + + // interpolant + let nn_max = 10; + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.set_data(uu).unwrap(); + + // find all roots in the interval + let solver = MultiRootSolverCheby::new(); + let roots = &solver.find(&interp).unwrap(); + let nroot = roots.len(); + assert_eq!(nroot, 0) } #[test] fn linear_function_no_roots_works() { // data let (xa, xb) = (0.0, 1.0); - let uu = Vector::from(&[0.5, 3.0]); + let uu = &[0.5, 3.0]; // interpolant - let nn_max = 100; + let nn_max = 10; let tol = 1e-8; let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); - interp.adapt_data(tol, uu.as_data()).unwrap(); - let nn = interp.get_degree(); + interp.adapt_data(tol, uu).unwrap(); // find all roots in the interval - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots = Vector::from(&solver.find(&interp).unwrap()); - let nroot = roots.dim(); + let solver = MultiRootSolverCheby::new(); + let roots = &solver.find(&interp).unwrap(); + let nroot = roots.len(); assert_eq!(nroot, 0) } @@ -638,8 +586,8 @@ mod tests { // data let (xa, xb) = (0.0, 1.0); let dx = xb - xa; - let uu = Vector::from(&[-7.0, -4.5, 0.5, 3.0]); - let np = uu.dim(); // number of points + let uu = &[-7.0, -4.5, 0.5, 3.0]; + let np = uu.len(); // number of points let nn = np - 1; // degree let mut xx_dat = Vector::new(np); let zz = InterpChebyshev::points(nn); @@ -651,21 +599,15 @@ mod tests { let nn_max = 100; let tol = 1e-8; let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); - interp.adapt_data(tol, uu.as_data()).unwrap(); - let nn = interp.get_degree(); + interp.adapt_data(tol, uu).unwrap(); // find all roots in the interval - let mut solver = MultiRootSolverCheby::new(nn).unwrap(); - let roots = Vector::from(&solver.find(&interp).unwrap()); - let f_at_roots = roots.get_mapped(|x| interp.eval(x).unwrap()); - let nroot = roots.dim(); - println!("roots =\n{}", roots); - for i in 0..nroot { - println!("xr = {}, f(xr) = {:.2e}", roots[i], f_at_roots[i]); - } + let solver = MultiRootSolverCheby::new(); + let roots = solver.find(&interp).unwrap(); + let nroot = roots.len(); assert_eq!(nroot, 1); approx_eq(roots[0], 0.7, 1e-15); - approx_eq(f_at_roots[0], 0.0, 1e-15); + approx_eq(interp.eval(roots[0]).unwrap(), 0.0, 1e-15); // plot /* @@ -685,7 +627,7 @@ mod tests { .set_marker_void(true); curve_dat.draw(xx_dat.as_data(), uu.as_data()); curve_int.draw(xx.as_data(), yy_int.as_data()); - curve_xr.draw(roots.as_data(), f_at_roots.as_data()); + curve_xr.draw(&roots, &vec![0.0]); let mut plot = Plot::new(); let mut legend = Legend::new(); legend.set_num_col(4); From 13ec7840634c57b04d569abb952a804560f2251c Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 17:41:36 +1000 Subject: [PATCH 73/93] Rename rootfinding file --- russell_lab/src/algo/mod.rs | 4 ++-- .../src/algo/{multi_root_solver_cheby.rs => rootfinding.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename russell_lab/src/algo/{multi_root_solver_cheby.rs => rootfinding.rs} (100%) diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index d8d1f1c7..20fabcb6 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -6,10 +6,10 @@ mod interp_lagrange; mod linear_fitting; mod min_bracketing; mod min_solver; -mod multi_root_solver_cheby; mod num_jacobian; mod quadrature; mod root_solver_brent; +mod rootfinding; mod testing; pub use crate::algo::common::*; pub use crate::algo::interp_chebyshev::*; @@ -17,8 +17,8 @@ pub use crate::algo::interp_lagrange::*; pub use crate::algo::linear_fitting::*; pub use crate::algo::min_bracketing::*; pub use crate::algo::min_solver::*; -pub use crate::algo::multi_root_solver_cheby::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; pub use crate::algo::root_solver_brent::*; +pub use crate::algo::rootfinding::*; pub use crate::algo::testing::*; diff --git a/russell_lab/src/algo/multi_root_solver_cheby.rs b/russell_lab/src/algo/rootfinding.rs similarity index 100% rename from russell_lab/src/algo/multi_root_solver_cheby.rs rename to russell_lab/src/algo/rootfinding.rs From 7547c7a727560e84476b38ed73e4c77b200c761b Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 17:42:59 +1000 Subject: [PATCH 74/93] Rename RootFinding struct --- .../examples/algo_multi_root_solver_cheby.rs | 2 +- russell_lab/src/algo/rootfinding.rs | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index fc4e1ef0..4b0fcf5e 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -18,7 +18,7 @@ fn main() -> Result<(), StrError> { println!("N = {}", nn); // find all roots in the interval - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots = Vector::from(&solver.find(&interp)?); let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); println!("roots =\n{}", roots); diff --git a/russell_lab/src/algo/rootfinding.rs b/russell_lab/src/algo/rootfinding.rs index 3656194e..e92272e4 100644 --- a/russell_lab/src/algo/rootfinding.rs +++ b/russell_lab/src/algo/rootfinding.rs @@ -21,7 +21,7 @@ use crate::{Matrix, Vector}; /// Analysis, 55(2):375-396. /// 3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy /// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 -pub struct MultiRootSolverCheby { +pub struct RootFinding { /// Holds the tolerance to avoid division by zero with the trailing Chebyshev coefficient /// /// Default = 1e-13 @@ -65,10 +65,10 @@ pub struct MultiRootSolverCheby { h_cen: f64, } -impl MultiRootSolverCheby { +impl RootFinding { /// Allocates a new instance pub fn new() -> Self { - MultiRootSolverCheby { + RootFinding { tol_zero_an: 1e-13, tol_rel_imag: 1.0e-8, tol_abs_boundary: TOL_RANGE / 10.0, @@ -111,7 +111,7 @@ impl MultiRootSolverCheby { /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval - /// let mut solver = MultiRootSolverCheby::new(); + /// let mut solver = RootFinding::new(); /// let roots = solver.find(&interp)?; /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); /// Ok(()) @@ -202,7 +202,7 @@ impl MultiRootSolverCheby { /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval - /// let mut solver = MultiRootSolverCheby::new(); + /// let mut solver = RootFinding::new(); /// let mut roots = solver.find(&interp)?; /// array_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate /// @@ -281,7 +281,7 @@ impl MultiRootSolverCheby { #[cfg(test)] mod tests { - use super::MultiRootSolverCheby; + use super::RootFinding; use crate::algo::NoArgs; use crate::InterpChebyshev; use crate::{approx_eq, array_approx_eq, get_test_functions}; @@ -361,7 +361,7 @@ mod tests { let (xa, xb) = (-4.0, 4.0); let nn = 2; let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); assert_eq!( solver.find(&interp).err(), Some("the interpolant must initialized first") @@ -376,7 +376,7 @@ mod tests { let args = &mut 0; let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); interp.set_function(nn, args, f).unwrap(); - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); assert_eq!( solver.find(&interp).err(), Some("the trailing Chebyshev coefficient vanishes; try a smaller degree N") @@ -396,7 +396,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots_unpolished = solver.find(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver @@ -430,7 +430,7 @@ mod tests { let args = &mut 0; let _ = f(0.0, args); let (xa, xb) = (-1.0, 1.0); - let mut solver = MultiRootSolverCheby::new(); + let mut solver = RootFinding::new(); let mut roots = Vec::new(); assert_eq!( solver.polish_roots_newton(&mut roots, xa, xb, args, f).err(), @@ -457,7 +457,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots_unpolished = solver.find(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver @@ -498,7 +498,7 @@ mod tests { let (xa, xb) = test.range; let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); interp.adapt_function(tol, args, test.f).unwrap(); - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots_unpolished = solver.find(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver @@ -556,7 +556,7 @@ mod tests { interp.set_data(uu).unwrap(); // find all roots in the interval - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots = &solver.find(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) @@ -575,7 +575,7 @@ mod tests { interp.adapt_data(tol, uu).unwrap(); // find all roots in the interval - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots = &solver.find(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) @@ -602,7 +602,7 @@ mod tests { interp.adapt_data(tol, uu).unwrap(); // find all roots in the interval - let solver = MultiRootSolverCheby::new(); + let solver = RootFinding::new(); let roots = solver.find(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 1); From 025ba55dc3c7bf8ff2c4027edba0984a3b42ccbc Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 17:43:18 +1000 Subject: [PATCH 75/93] Rename root_finding file --- russell_lab/src/algo/{rootfinding.rs => root_finding.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename russell_lab/src/algo/{rootfinding.rs => root_finding.rs} (100%) diff --git a/russell_lab/src/algo/rootfinding.rs b/russell_lab/src/algo/root_finding.rs similarity index 100% rename from russell_lab/src/algo/rootfinding.rs rename to russell_lab/src/algo/root_finding.rs From eff27e7cb29d380d35336ba8a997896ac2507713 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 17:43:25 +1000 Subject: [PATCH 76/93] Rename root_finding file --- russell_lab/src/algo/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index 20fabcb6..25fb888d 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -8,8 +8,8 @@ mod min_bracketing; mod min_solver; mod num_jacobian; mod quadrature; +mod root_finding; mod root_solver_brent; -mod rootfinding; mod testing; pub use crate::algo::common::*; pub use crate::algo::interp_chebyshev::*; @@ -19,6 +19,6 @@ pub use crate::algo::min_bracketing::*; pub use crate::algo::min_solver::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; +pub use crate::algo::root_finding::*; pub use crate::algo::root_solver_brent::*; -pub use crate::algo::rootfinding::*; pub use crate::algo::testing::*; From 5ddbe0595c85e8a990b1fb377c101a86808f792e Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 17:43:59 +1000 Subject: [PATCH 77/93] Rename root_finding_brent file --- russell_lab/src/algo/mod.rs | 4 ++-- .../src/algo/{root_solver_brent.rs => root_finding_brent.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename russell_lab/src/algo/{root_solver_brent.rs => root_finding_brent.rs} (100%) diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index 25fb888d..7272cd38 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -9,7 +9,7 @@ mod min_solver; mod num_jacobian; mod quadrature; mod root_finding; -mod root_solver_brent; +mod root_finding_brent; mod testing; pub use crate::algo::common::*; pub use crate::algo::interp_chebyshev::*; @@ -20,5 +20,5 @@ pub use crate::algo::min_solver::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; pub use crate::algo::root_finding::*; -pub use crate::algo::root_solver_brent::*; +pub use crate::algo::root_finding_brent::*; pub use crate::algo::testing::*; diff --git a/russell_lab/src/algo/root_solver_brent.rs b/russell_lab/src/algo/root_finding_brent.rs similarity index 100% rename from russell_lab/src/algo/root_solver_brent.rs rename to russell_lab/src/algo/root_finding_brent.rs From a603d45b5f2542ad33f0225365fc0a6e713c6524 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 18:26:03 +1000 Subject: [PATCH 78/93] Move Brent solver to RootFinding --- .../algo_min_and_root_solver_brent.rs | 4 +- .../examples/algo_multi_root_solver_cheby.rs | 2 +- russell_lab/src/algo/mod.rs | 1 - russell_lab/src/algo/root_finding.rs | 36 +++-- russell_lab/src/algo/root_finding_brent.rs | 128 +++++------------- 5 files changed, 58 insertions(+), 113 deletions(-) diff --git a/russell_lab/examples/algo_min_and_root_solver_brent.rs b/russell_lab/examples/algo_min_and_root_solver_brent.rs index cc40ef0d..1f108d7a 100644 --- a/russell_lab/examples/algo_min_and_root_solver_brent.rs +++ b/russell_lab/examples/algo_min_and_root_solver_brent.rs @@ -13,8 +13,8 @@ fn main() -> Result<(), StrError> { println!("\n{}", stats); // root - let solver = RootSolverBrent::new(); - let (xo, stats) = solver.find(0.3, 0.4, args, |x, _| { + let solver = RootFinding::new(); + let (xo, stats) = solver.brent_find(0.3, 0.4, args, |x, _| { Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5) })?; println!("\nx_root = {:?}", xo); diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 4b0fcf5e..730cb139 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), StrError> { // find all roots in the interval let solver = RootFinding::new(); - let roots = Vector::from(&solver.find(&interp)?); + let roots = Vector::from(&solver.cheby_find(&interp)?); let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); println!("roots =\n{}", roots); println!("f @ roots =\n{}", print_vec_exp(&f_at_roots)); diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index 7272cd38..36065287 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -20,5 +20,4 @@ pub use crate::algo::min_solver::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; pub use crate::algo::root_finding::*; -pub use crate::algo::root_finding_brent::*; pub use crate::algo::testing::*; diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index e92272e4..f801c492 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -58,6 +58,16 @@ pub struct RootFinding { /// Default = 15 pub newton_max_iterations: usize, + /// Max number of iterations for Brent's method + /// + /// Default = 100 + pub brent_max_iterations: usize, + + /// Tolerance for Brent's method + /// + /// Default = 1e-13 + pub brent_tolerance: f64, + /// Stepsize for one-sided differences h_osd: f64, @@ -75,12 +85,14 @@ impl RootFinding { newton_tol_zero_dx: 1e-13, newton_tol_zero_fx: 1e-13, newton_max_iterations: 15, + brent_max_iterations: 100, + brent_tolerance: 1e-13, h_osd: f64::powf(f64::EPSILON, 1.0 / 2.0), h_cen: f64::powf(f64::EPSILON, 1.0 / 3.0), } } - /// Find all roots in the interval + /// Find all roots in the interval using Chebyshev interpolation /// /// # Input /// @@ -112,12 +124,12 @@ impl RootFinding { /// /// // find all roots in the interval /// let mut solver = RootFinding::new(); - /// let roots = solver.find(&interp)?; + /// let roots = solver.cheby_find(&interp)?; /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); /// Ok(()) /// } /// ``` - pub fn find(&self, interp: &InterpChebyshev) -> Result, StrError> { + pub fn cheby_find(&self, interp: &InterpChebyshev) -> Result, StrError> { // check if !interp.is_ready() { return Err("the interpolant must initialized first"); @@ -203,7 +215,7 @@ impl RootFinding { /// /// // find all roots in the interval /// let mut solver = RootFinding::new(); - /// let mut roots = solver.find(&interp)?; + /// let mut roots = solver.cheby_find(&interp)?; /// array_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate /// /// // polish the roots @@ -363,7 +375,7 @@ mod tests { let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); let solver = RootFinding::new(); assert_eq!( - solver.find(&interp).err(), + solver.cheby_find(&interp).err(), Some("the interpolant must initialized first") ); } @@ -378,7 +390,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); let solver = RootFinding::new(); assert_eq!( - solver.find(&interp).err(), + solver.cheby_find(&interp).err(), Some("the trailing Chebyshev coefficient vanishes; try a smaller degree N") ); } @@ -397,7 +409,7 @@ mod tests { // find roots let solver = RootFinding::new(); - let roots_unpolished = solver.find(&interp).unwrap(); + let roots_unpolished = solver.cheby_find(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver .polish_roots_newton(&mut roots_polished, xa, xb, args, f) @@ -458,7 +470,7 @@ mod tests { // find roots let solver = RootFinding::new(); - let roots_unpolished = solver.find(&interp).unwrap(); + let roots_unpolished = solver.cheby_find(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver .polish_roots_newton(&mut roots_polished, xa, xb, args, f) @@ -499,7 +511,7 @@ mod tests { let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); interp.adapt_function(tol, args, test.f).unwrap(); let solver = RootFinding::new(); - let roots_unpolished = solver.find(&interp).unwrap(); + let roots_unpolished = solver.cheby_find(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver .polish_roots_newton(&mut roots_polished, xa, xb, args, test.f) @@ -557,7 +569,7 @@ mod tests { // find all roots in the interval let solver = RootFinding::new(); - let roots = &solver.find(&interp).unwrap(); + let roots = &solver.cheby_find(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) } @@ -576,7 +588,7 @@ mod tests { // find all roots in the interval let solver = RootFinding::new(); - let roots = &solver.find(&interp).unwrap(); + let roots = &solver.cheby_find(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) } @@ -603,7 +615,7 @@ mod tests { // find all roots in the interval let solver = RootFinding::new(); - let roots = solver.find(&interp).unwrap(); + let roots = solver.cheby_find(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 1); approx_eq(roots[0], 0.7, 1e-15); diff --git a/russell_lab/src/algo/root_finding_brent.rs b/russell_lab/src/algo/root_finding_brent.rs index eac27c1c..914f3553 100644 --- a/russell_lab/src/algo/root_finding_brent.rs +++ b/russell_lab/src/algo/root_finding_brent.rs @@ -1,43 +1,8 @@ -use super::Stats; +use super::{RootFinding, Stats}; use crate::StrError; -/// Implements Brent's method for finding a bracketed root of an equation -#[derive(Clone, Debug)] -pub struct RootSolverBrent { - /// Max number of iterations - /// - /// ```text - /// n_iteration_max ≥ 2 - /// ``` - pub n_iteration_max: usize, - - /// Tolerance - /// - /// e.g., 1e-10 - pub tolerance: f64, -} - -impl RootSolverBrent { - /// Allocates a new instance - pub fn new() -> Self { - RootSolverBrent { - n_iteration_max: 100, - tolerance: 1e-10, - } - } - - /// Validates the parameters - fn validate_params(&self) -> Result<(), StrError> { - if self.n_iteration_max < 2 { - return Err("n_iteration_max must be ≥ 2"); - } - if self.tolerance < 10.0 * f64::EPSILON { - return Err("the tolerance must be ≥ 10.0 * f64::EPSILON"); - } - Ok(()) - } - - /// Employs Brent's method to find the roots of an equation +impl RootFinding { + /// Employs Brent's method to find a single root of an equation /// /// See: /// @@ -45,11 +10,10 @@ impl RootSolverBrent { /// /// # Input /// - /// * `xa` -- initial "bracket" coordinate such that `f(xa) × f(xb) < 0` - /// * `xb` -- initial "bracket" coordinate such that `f(xa) × f(xb) < 0` - /// * `params` -- optional control parameters + /// * `xa` -- lower bound such that `f(xa) × f(xb) < 0` + /// * `xb` -- upper bound such that `f(xa) × f(xb) < 0` /// * `args` -- extra arguments for the callback function - /// * `f` -- is the callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. + /// * `f` -- callback function implementing `f(x)` as `f(x, args)`; it returns `f @ x` or it may return an error. /// /// **Note:** `xa` must be different from `xb` /// @@ -63,20 +27,20 @@ impl RootSolverBrent { /// # Examples /// /// ``` - /// use russell_lab::{approx_eq, RootSolverBrent, StrError}; + /// use russell_lab::*; /// /// fn main() -> Result<(), StrError> { /// let args = &mut 0; - /// let solver = RootSolverBrent::new(); + /// let solver = RootFinding::new(); /// let (xa, xb) = (-4.0, 0.0); - /// let (xo, stats) = solver.find(xa, xb, args, |x, _| Ok(4.0 - x * x))?; + /// let (xo, stats) = solver.brent_find(xa, xb, args, |x, _| Ok(4.0 - x * x))?; /// println!("\nroot = {:?}", xo); /// println!("\n{}", stats); - /// approx_eq(xo, -2.0, 1e-10); + /// approx_eq(xo, -2.0, 1e-14); /// Ok(()) /// } /// ``` - pub fn find(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(f64, Stats), StrError> + pub fn brent_find(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(f64, Stats), StrError> where F: FnMut(f64, &mut A) -> Result, { @@ -111,9 +75,6 @@ impl RootSolverBrent { return Err("xa must be different from xb"); } - // validate the parameters - self.validate_params()?; - // allocate stats struct let mut stats = Stats::new(); @@ -131,7 +92,7 @@ impl RootSolverBrent { // solve let mut converged = false; - for _ in 0..self.n_iteration_max { + for _ in 0..self.brent_max_iterations { stats.n_iterations += 1; // old step @@ -148,7 +109,7 @@ impl RootSolverBrent { } // tolerance - let tol = 2.0 * f64::EPSILON * f64::abs(b) + self.tolerance / 2.0; + let tol = 2.0 * f64::EPSILON * f64::abs(b) + self.brent_tolerance / 2.0; // new step let mut step_new = (c - b) / 2.0; @@ -233,47 +194,33 @@ impl RootSolverBrent { #[cfg(test)] mod tests { - use super::RootSolverBrent; use crate::algo::testing::get_test_functions; - use crate::algo::NoArgs; use crate::approx_eq; + use crate::{NoArgs, RootFinding}; #[test] - fn validate_params_captures_errors() { - let mut solver = RootSolverBrent::new(); - solver.n_iteration_max = 0; - assert_eq!(solver.validate_params().err(), Some("n_iteration_max must be ≥ 2")); - solver.n_iteration_max = 2; - solver.tolerance = 0.0; - assert_eq!( - solver.validate_params().err(), - Some("the tolerance must be ≥ 10.0 * f64::EPSILON") - ); - } - - #[test] - fn brent_captures_errors_1() { + fn brent_find_captures_errors_1() { let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; - let mut solver = RootSolverBrent::new(); + let mut solver = RootFinding::new(); assert_eq!(f(1.0, args).unwrap(), 0.0); assert_eq!( - solver.find(-0.5, -0.5, args, f).err(), + solver.brent_find(-0.5, -0.5, args, f).err(), Some("xa must be different from xb") ); assert_eq!( - solver.find(-0.5, -0.5 - 10.0 * f64::EPSILON, args, f).err(), + solver.brent_find(-0.5, -0.5 - 10.0 * f64::EPSILON, args, f).err(), Some("xa and xb must bracket the root and f(xa) × f(xb) < 0") ); - solver.n_iteration_max = 0; + solver.brent_max_iterations = 0; assert_eq!( - solver.find(-0.5, 2.0, args, f).err(), - Some("n_iteration_max must be ≥ 2") + solver.brent_find(0.0, 2.0, args, f).err(), + Some("brent solver failed to converge") ); } #[test] - fn brent_captures_errors_2() { + fn brent_find_captures_errors_2() { struct Args { count: usize, target: usize, @@ -288,42 +235,42 @@ mod tests { res }; let args = &mut Args { count: 0, target: 0 }; - let solver = RootSolverBrent::new(); + let solver = RootFinding::new(); // first function call - assert_eq!(solver.find(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.brent_find(-0.5, 2.0, args, f).err(), Some("stop")); // second function call args.count = 0; args.target = 1; - assert_eq!(solver.find(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.brent_find(-0.5, 2.0, args, f).err(), Some("stop")); // third function call args.count = 0; args.target = 2; - assert_eq!(solver.find(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.brent_find(-0.5, 2.0, args, f).err(), Some("stop")); } #[test] - fn brent_works_1() { + fn brent_find_works() { let args = &mut 0; - let solver = RootSolverBrent::new(); + let solver = RootFinding::new(); for test in &get_test_functions() { println!("\n==================================================================="); println!("\n{}", test.name); if let Some(bracket) = test.root1 { - let (xo, stats) = solver.find(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.brent_find(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-11); approx_eq((test.f)(xo, args).unwrap(), 0.0, test.tol_root); } if let Some(bracket) = test.root2 { - let (xo, stats) = solver.find(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.brent_find(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-11); approx_eq((test.f)(xo, args).unwrap(), 0.0, test.tol_root); } if let Some(bracket) = test.root3 { - let (xo, stats) = solver.find(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.brent_find(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-13); @@ -332,17 +279,4 @@ mod tests { } println!("\n===================================================================\n"); } - - #[test] - fn brent_fails_on_non_converged() { - let f = |x, _: &mut NoArgs| Ok(f64::powi(x - 1.0, 2) + 5.0 * f64::sin(x)); - let args = &mut 0; - assert!(f(1.0, args).unwrap() > 0.0); - let mut solver = RootSolverBrent::new(); - solver.n_iteration_max = 2; - assert_eq!( - solver.find(-2.0, -0.7, args, f).err(), - Some("brent solver failed to converge") - ); - } } From f6d752cacdf00126fea26f4a2e503f6209cd23c4 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 18:27:12 +1000 Subject: [PATCH 79/93] Rename brent_find to brent in RootFinding --- .../algo_min_and_root_solver_brent.rs | 2 +- russell_lab/src/algo/root_finding_brent.rs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/russell_lab/examples/algo_min_and_root_solver_brent.rs b/russell_lab/examples/algo_min_and_root_solver_brent.rs index 1f108d7a..351b2ab8 100644 --- a/russell_lab/examples/algo_min_and_root_solver_brent.rs +++ b/russell_lab/examples/algo_min_and_root_solver_brent.rs @@ -14,7 +14,7 @@ fn main() -> Result<(), StrError> { // root let solver = RootFinding::new(); - let (xo, stats) = solver.brent_find(0.3, 0.4, args, |x, _| { + let (xo, stats) = solver.brent(0.3, 0.4, args, |x, _| { Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5) })?; println!("\nx_root = {:?}", xo); diff --git a/russell_lab/src/algo/root_finding_brent.rs b/russell_lab/src/algo/root_finding_brent.rs index 914f3553..e0bbc9ba 100644 --- a/russell_lab/src/algo/root_finding_brent.rs +++ b/russell_lab/src/algo/root_finding_brent.rs @@ -33,14 +33,14 @@ impl RootFinding { /// let args = &mut 0; /// let solver = RootFinding::new(); /// let (xa, xb) = (-4.0, 0.0); - /// let (xo, stats) = solver.brent_find(xa, xb, args, |x, _| Ok(4.0 - x * x))?; + /// let (xo, stats) = solver.brent(xa, xb, args, |x, _| Ok(4.0 - x * x))?; /// println!("\nroot = {:?}", xo); /// println!("\n{}", stats); /// approx_eq(xo, -2.0, 1e-14); /// Ok(()) /// } /// ``` - pub fn brent_find(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(f64, Stats), StrError> + pub fn brent(&self, xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(f64, Stats), StrError> where F: FnMut(f64, &mut A) -> Result, { @@ -205,16 +205,16 @@ mod tests { let mut solver = RootFinding::new(); assert_eq!(f(1.0, args).unwrap(), 0.0); assert_eq!( - solver.brent_find(-0.5, -0.5, args, f).err(), + solver.brent(-0.5, -0.5, args, f).err(), Some("xa must be different from xb") ); assert_eq!( - solver.brent_find(-0.5, -0.5 - 10.0 * f64::EPSILON, args, f).err(), + solver.brent(-0.5, -0.5 - 10.0 * f64::EPSILON, args, f).err(), Some("xa and xb must bracket the root and f(xa) × f(xb) < 0") ); solver.brent_max_iterations = 0; assert_eq!( - solver.brent_find(0.0, 2.0, args, f).err(), + solver.brent(0.0, 2.0, args, f).err(), Some("brent solver failed to converge") ); } @@ -237,15 +237,15 @@ mod tests { let args = &mut Args { count: 0, target: 0 }; let solver = RootFinding::new(); // first function call - assert_eq!(solver.brent_find(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); // second function call args.count = 0; args.target = 1; - assert_eq!(solver.brent_find(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); // third function call args.count = 0; args.target = 2; - assert_eq!(solver.brent_find(-0.5, 2.0, args, f).err(), Some("stop")); + assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); } #[test] @@ -256,21 +256,21 @@ mod tests { println!("\n==================================================================="); println!("\n{}", test.name); if let Some(bracket) = test.root1 { - let (xo, stats) = solver.brent_find(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.brent(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-11); approx_eq((test.f)(xo, args).unwrap(), 0.0, test.tol_root); } if let Some(bracket) = test.root2 { - let (xo, stats) = solver.brent_find(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.brent(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-11); approx_eq((test.f)(xo, args).unwrap(), 0.0, test.tol_root); } if let Some(bracket) = test.root3 { - let (xo, stats) = solver.brent_find(bracket.a, bracket.b, args, test.f).unwrap(); + let (xo, stats) = solver.brent(bracket.a, bracket.b, args, test.f).unwrap(); println!("\nxo = {:?}", xo); println!("\n{}", stats); approx_eq(xo, bracket.xo, 1e-13); From 684fd151b16cb8c417c8392e2837512589d5b29d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 18:28:49 +1000 Subject: [PATCH 80/93] Rename cheby_find to chebyshev in RootFinding --- .../examples/algo_multi_root_solver_cheby.rs | 2 +- russell_lab/src/algo/root_finding.rs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 730cb139..2a2af593 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), StrError> { // find all roots in the interval let solver = RootFinding::new(); - let roots = Vector::from(&solver.cheby_find(&interp)?); + let roots = Vector::from(&solver.chebyshev(&interp)?); let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); println!("roots =\n{}", roots); println!("f @ roots =\n{}", print_vec_exp(&f_at_roots)); diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index f801c492..67612da2 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -124,12 +124,12 @@ impl RootFinding { /// /// // find all roots in the interval /// let mut solver = RootFinding::new(); - /// let roots = solver.cheby_find(&interp)?; + /// let roots = solver.chebyshev(&interp)?; /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); /// Ok(()) /// } /// ``` - pub fn cheby_find(&self, interp: &InterpChebyshev) -> Result, StrError> { + pub fn chebyshev(&self, interp: &InterpChebyshev) -> Result, StrError> { // check if !interp.is_ready() { return Err("the interpolant must initialized first"); @@ -215,7 +215,7 @@ impl RootFinding { /// /// // find all roots in the interval /// let mut solver = RootFinding::new(); - /// let mut roots = solver.cheby_find(&interp)?; + /// let mut roots = solver.chebyshev(&interp)?; /// array_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate /// /// // polish the roots @@ -375,7 +375,7 @@ mod tests { let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); let solver = RootFinding::new(); assert_eq!( - solver.cheby_find(&interp).err(), + solver.chebyshev(&interp).err(), Some("the interpolant must initialized first") ); } @@ -390,7 +390,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); let solver = RootFinding::new(); assert_eq!( - solver.cheby_find(&interp).err(), + solver.chebyshev(&interp).err(), Some("the trailing Chebyshev coefficient vanishes; try a smaller degree N") ); } @@ -409,7 +409,7 @@ mod tests { // find roots let solver = RootFinding::new(); - let roots_unpolished = solver.cheby_find(&interp).unwrap(); + let roots_unpolished = solver.chebyshev(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver .polish_roots_newton(&mut roots_polished, xa, xb, args, f) @@ -470,7 +470,7 @@ mod tests { // find roots let solver = RootFinding::new(); - let roots_unpolished = solver.cheby_find(&interp).unwrap(); + let roots_unpolished = solver.chebyshev(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver .polish_roots_newton(&mut roots_polished, xa, xb, args, f) @@ -511,7 +511,7 @@ mod tests { let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); interp.adapt_function(tol, args, test.f).unwrap(); let solver = RootFinding::new(); - let roots_unpolished = solver.cheby_find(&interp).unwrap(); + let roots_unpolished = solver.chebyshev(&interp).unwrap(); let mut roots_polished = roots_unpolished.clone(); solver .polish_roots_newton(&mut roots_polished, xa, xb, args, test.f) @@ -569,7 +569,7 @@ mod tests { // find all roots in the interval let solver = RootFinding::new(); - let roots = &solver.cheby_find(&interp).unwrap(); + let roots = &solver.chebyshev(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) } @@ -588,7 +588,7 @@ mod tests { // find all roots in the interval let solver = RootFinding::new(); - let roots = &solver.cheby_find(&interp).unwrap(); + let roots = &solver.chebyshev(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) } @@ -615,7 +615,7 @@ mod tests { // find all roots in the interval let solver = RootFinding::new(); - let roots = solver.cheby_find(&interp).unwrap(); + let roots = solver.chebyshev(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 1); approx_eq(roots[0], 0.7, 1e-15); From c9af9b14704142f3c0668a97ce58eff66618018d Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 18:36:59 +1000 Subject: [PATCH 81/93] Rename polish roots to refine --- russell_lab/README.md | 4 +- .../examples/algo_multi_root_solver_cheby.rs | 28 +++--- russell_lab/src/algo/root_finding.rs | 95 ++++++++----------- 3 files changed, 57 insertions(+), 70 deletions(-) diff --git a/russell_lab/README.md b/russell_lab/README.md index b335d790..b0ec1778 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -507,7 +507,7 @@ f @ roots = -1.16e-8 -5.80e-9 -polished roots = +refined roots = ┌ ┐ │ 0.04109147155278252 │ │ 0.15301723213859994 │ @@ -516,7 +516,7 @@ polished roots = │ 0.47590538689192813 │ │ 0.5162732665558162 │ └ ┘ -f @ polished roots = +f @ refined roots = 6.66e-16 -2.22e-16 -2.22e-16 diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 2a2af593..28f27b26 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -25,11 +25,11 @@ fn main() -> Result<(), StrError> { println!("f @ roots =\n{}", print_vec_exp(&f_at_roots)); // polish the roots - let mut roots_polished = roots.clone(); - solver.polish_roots_newton(roots_polished.as_mut_data(), xa, xb, args, f)?; - let f_at_roots_polished = roots_polished.get_mapped(|x| f(x, args).unwrap()); - println!("polished roots =\n{}", roots_polished); - println!("f @ polished roots =\n{}", print_vec_exp(&f_at_roots_polished)); + let mut roots_refined = roots.clone(); + solver.refine(roots_refined.as_mut_data(), xa, xb, args, f)?; + let f_at_roots_refined = roots_refined.get_mapped(|x| f(x, args).unwrap()); + println!("refined roots =\n{}", roots_refined); + println!("f @ refined roots =\n{}", print_vec_exp(&f_at_roots_refined)); // plot the results let nstation = 301; @@ -38,30 +38,30 @@ fn main() -> Result<(), StrError> { let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); let mut curve_ana = Curve::new(); let mut curve_int = Curve::new(); - let mut zeros_unpolished = Curve::new(); - let mut zeros_polished = Curve::new(); + let mut zeros = Curve::new(); + let mut zeros_refined = Curve::new(); curve_ana.set_label("analytical"); curve_int .set_label("interpolated") .set_line_style("--") .set_marker_style(".") .set_marker_every(5); - zeros_unpolished + zeros .set_marker_style("o") .set_marker_void(true) .set_marker_line_color("#00760F") .set_line_style("None"); - zeros_polished + zeros_refined .set_marker_style("s") .set_marker_size(10.0) .set_marker_void(true) .set_marker_line_color("#00760F") .set_line_style("None"); for root in &roots { - zeros_unpolished.draw(&[*root], &[interp.eval(*root).unwrap()]); + zeros.draw(&[*root], &[interp.eval(*root).unwrap()]); } - for root in &roots_polished { - zeros_polished.draw(&[*root], &[f(*root, args).unwrap()]); + for root in &roots_refined { + zeros_refined.draw(&[*root], &[f(*root, args).unwrap()]); } curve_int.draw(xx.as_data(), yy_int.as_data()); curve_ana.draw(xx.as_data(), yy_ana.as_data()); @@ -72,8 +72,8 @@ fn main() -> Result<(), StrError> { legend.draw(); plot.add(&curve_ana) .add(&curve_int) - .add(&zeros_unpolished) - .add(&zeros_polished) + .add(&zeros) + .add(&zeros_refined) .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index 67612da2..c75677c7 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -195,7 +195,7 @@ impl RootFinding { Ok(roots) } - /// Polishes the roots using Newton's method + /// Refines the roots using Newton's method /// /// # Examples /// @@ -224,14 +224,7 @@ impl RootFinding { /// Ok(()) /// } /// ``` - pub fn polish_roots_newton( - &self, - roots: &mut [f64], - xa: f64, - xb: f64, - args: &mut A, - mut f: F, - ) -> Result<(), StrError> + pub fn refine(&self, roots: &mut [f64], xa: f64, xb: f64, args: &mut A, mut f: F) -> Result<(), StrError> where F: FnMut(f64, &mut A) -> Result, { @@ -308,8 +301,8 @@ mod tests { fn graph( name: &str, interp: &InterpChebyshev, - roots_unpolished: &[f64], - roots_polished: &[f64], + roots: &[f64], + roots_refined: &[f64], args: &mut A, mut f: F, nstation: usize, @@ -323,30 +316,30 @@ mod tests { let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); let mut curve_ana = Curve::new(); let mut curve_int = Curve::new(); - let mut zeros_unpolished = Curve::new(); - let mut zeros_polished = Curve::new(); + let mut zeros = Curve::new(); + let mut zeros_refined = Curve::new(); curve_ana.set_label("analytical"); curve_int .set_label("interpolated") .set_line_style("--") .set_marker_style(".") .set_marker_every(5); - zeros_unpolished + zeros .set_marker_style("o") .set_marker_void(true) .set_marker_line_color("#00760F") .set_line_style("None"); - zeros_polished + zeros_refined .set_marker_style("s") .set_marker_size(10.0) .set_marker_void(true) .set_marker_line_color("#00760F") .set_line_style("None"); - for root in roots_unpolished { - zeros_unpolished.draw(&[*root], &[interp.eval(*root).unwrap()]); + for root in roots { + zeros.draw(&[*root], &[interp.eval(*root).unwrap()]); } - for root in roots_polished { - zeros_polished.draw(&[*root], &[f(*root, args).unwrap()]); + for root in roots_refined { + zeros_refined.draw(&[*root], &[f(*root, args).unwrap()]); } curve_int.draw(xx.as_data(), yy_int.as_data()); curve_ana.draw(xx.as_data(), yy_ana.as_data()); @@ -357,8 +350,8 @@ mod tests { legend.draw(); plot.add(&curve_ana) .add(&curve_int) - .add(&zeros_unpolished) - .add(&zeros_polished) + .add(&zeros) + .add(&zeros_refined) .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") @@ -409,22 +402,20 @@ mod tests { // find roots let solver = RootFinding::new(); - let roots_unpolished = solver.chebyshev(&interp).unwrap(); - let mut roots_polished = roots_unpolished.clone(); - solver - .polish_roots_newton(&mut roots_polished, xa, xb, args, f) - .unwrap(); - println!("n_roots = {}", roots_polished.len()); - println!("roots_unpolished = {:?}", roots_unpolished); - println!("roots_polished = {:?}", roots_polished); + let roots = solver.chebyshev(&interp).unwrap(); + let mut roots_refined = roots.clone(); + solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); + println!("n_roots = {}", roots_refined.len()); + println!("roots = {:?}", roots); + println!("roots_refined = {:?}", roots_refined); // figure /* graph( "test_multi_root_solver_cheby_simple", &interp, - &roots_unpolished, - &roots_polished, + &roots, + &roots_refined, args, f, 101, @@ -433,7 +424,7 @@ mod tests { */ // check - array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); + array_approx_eq(&roots_refined, &[-1.0, 1.0], 1e-14); } #[test] @@ -445,13 +436,13 @@ mod tests { let mut solver = RootFinding::new(); let mut roots = Vec::new(); assert_eq!( - solver.polish_roots_newton(&mut roots, xa, xb, args, f).err(), + solver.refine(&mut roots, xa, xb, args, f).err(), Some("at least one root is required") ); let mut roots = [0.0]; solver.newton_max_iterations = 0; assert_eq!( - solver.polish_roots_newton(&mut roots, xa, xb, args, f).err(), + solver.refine(&mut roots, xa, xb, args, f).err(), Some("Newton's method did not converge") ); } @@ -470,22 +461,20 @@ mod tests { // find roots let solver = RootFinding::new(); - let roots_unpolished = solver.chebyshev(&interp).unwrap(); - let mut roots_polished = roots_unpolished.clone(); - solver - .polish_roots_newton(&mut roots_polished, xa, xb, args, f) - .unwrap(); - println!("n_roots = {}", roots_polished.len()); - println!("roots_unpolished = {:?}", roots_unpolished); - println!("roots_polished = {:?}", roots_polished); + let roots = solver.chebyshev(&interp).unwrap(); + let mut roots_refined = roots.clone(); + solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); + println!("n_roots = {}", roots_refined.len()); + println!("roots = {:?}", roots); + println!("roots_refined = {:?}", roots_refined); // figure /* graph( "test_polish_roots_newton", &interp, - &roots_unpolished, - &roots_polished, + &roots, + &roots_refined, args, f, 101, @@ -494,7 +483,7 @@ mod tests { */ // check - array_approx_eq(&roots_polished, &[-1.0, 1.0], 1e-14); + array_approx_eq(&roots_refined, &[-1.0, 1.0], 1e-14); } #[test] @@ -511,12 +500,10 @@ mod tests { let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); interp.adapt_function(tol, args, test.f).unwrap(); let solver = RootFinding::new(); - let roots_unpolished = solver.chebyshev(&interp).unwrap(); - let mut roots_polished = roots_unpolished.clone(); - solver - .polish_roots_newton(&mut roots_polished, xa, xb, args, test.f) - .unwrap(); - for xr in &roots_polished { + let roots = solver.chebyshev(&interp).unwrap(); + let mut roots_refined = roots.clone(); + solver.refine(&mut roots_refined, xa, xb, args, test.f).unwrap(); + for xr in &roots_refined { let fx = (test.f)(*xr, args).unwrap(); println!("x = {}, f(x) = {:.2e}", xr, fx); assert!(fx < 1e-10); @@ -537,7 +524,7 @@ mod tests { } } if *id == 9 { - assert_eq!(roots_unpolished.len(), 93); + assert_eq!(roots.len(), 93); } // figure /* @@ -545,8 +532,8 @@ mod tests { graph( &format!("test_multi_root_solver_cheby_{:0>3}", id), &interp, - &roots_unpolished, - &roots_polished, + &roots, + &roots_refined, args, test.f, nstation, From 6252cecadef44b1435bd9c72763e22ffd00f0c79 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 19:16:05 +1000 Subject: [PATCH 82/93] Impl vec_fmt_scientific --- russell_lab/README.md | 29 ++++---- .../examples/algo_multi_root_solver_cheby.rs | 13 +--- russell_lab/src/vector/mod.rs | 2 + russell_lab/src/vector/vec_fmt_scientific.rs | 70 +++++++++++++++++++ 4 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 russell_lab/src/vector/vec_fmt_scientific.rs diff --git a/russell_lab/README.md b/russell_lab/README.md index b0ec1778..3aff616a 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -500,13 +500,14 @@ roots = │ 0.5162732673126048 │ └ ┘ f @ roots = - 1.84e-8 - -1.51e-8 - -2.40e-8 - 9.53e-9 - -1.16e-8 - -5.80e-9 - +┌ ┐ +│ 1.84E-08 │ +│ -1.51E-08 │ +│ -2.40E-08 │ +│ 9.53E-09 │ +│ -1.16E-08 │ +│ -5.80E-09 │ +└ ┘ refined roots = ┌ ┐ │ 0.04109147155278252 │ @@ -517,12 +518,14 @@ refined roots = │ 0.5162732665558162 │ └ ┘ f @ refined roots = - 6.66e-16 --2.22e-16 --2.22e-16 - 1.33e-15 - 4.44e-16 --2.22e-16 +┌ ┐ +│ 6.66E-16 │ +│ -2.22E-16 │ +│ -2.22E-16 │ +│ 1.33E-15 │ +│ 4.44E-16 │ +│ -2.22E-16 │ +└ ┘ ``` The function and the roots are illustrated in the figure below. diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index 28f27b26..ecd0d60f 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -1,7 +1,6 @@ use plotpy::{Curve, Legend, Plot}; use russell_lab::math::PI; use russell_lab::*; -use std::fmt::Write; fn main() -> Result<(), StrError> { // function @@ -22,14 +21,14 @@ fn main() -> Result<(), StrError> { let roots = Vector::from(&solver.chebyshev(&interp)?); let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); println!("roots =\n{}", roots); - println!("f @ roots =\n{}", print_vec_exp(&f_at_roots)); + println!("f @ roots =\n{}", vec_fmt_scientific(&f_at_roots, 2)); // polish the roots let mut roots_refined = roots.clone(); solver.refine(roots_refined.as_mut_data(), xa, xb, args, f)?; let f_at_roots_refined = roots_refined.get_mapped(|x| f(x, args).unwrap()); println!("refined roots =\n{}", roots_refined); - println!("f @ refined roots =\n{}", print_vec_exp(&f_at_roots_refined)); + println!("f @ refined roots =\n{}", vec_fmt_scientific(&f_at_roots_refined, 2)); // plot the results let nstation = 301; @@ -81,11 +80,3 @@ fn main() -> Result<(), StrError> { .unwrap(); Ok(()) } - -fn print_vec_exp(v: &Vector) -> String { - let mut buf = String::new(); - for x in v { - writeln!(&mut buf, "{:>9.2e}", x).unwrap(); - } - buf -} diff --git a/russell_lab/src/vector/mod.rs b/russell_lab/src/vector/mod.rs index e20f2fdf..b66548bf 100644 --- a/russell_lab/src/vector/mod.rs +++ b/russell_lab/src/vector/mod.rs @@ -14,6 +14,7 @@ mod vec_add; mod vec_all_finite; mod vec_approx_eq; mod vec_copy; +mod vec_fmt_scientific; mod vec_inner; mod vec_max_abs_diff; mod vec_max_scaled; @@ -35,6 +36,7 @@ pub use crate::vector::vec_add::*; pub use crate::vector::vec_all_finite::*; pub use crate::vector::vec_approx_eq::*; pub use crate::vector::vec_copy::*; +pub use crate::vector::vec_fmt_scientific::*; pub use crate::vector::vec_inner::*; pub use crate::vector::vec_max_abs_diff::*; pub use crate::vector::vec_max_scaled::*; diff --git a/russell_lab/src/vector/vec_fmt_scientific.rs b/russell_lab/src/vector/vec_fmt_scientific.rs new file mode 100644 index 00000000..a7144204 --- /dev/null +++ b/russell_lab/src/vector/vec_fmt_scientific.rs @@ -0,0 +1,70 @@ +use super::Vector; +use crate::format_scientific; +use std::cmp; +use std::fmt::Write; + +pub fn vec_fmt_scientific(u: &Vector, precision: usize) -> String { + let mut result = String::new(); + let f = &mut result; + // handle empty vector + if u.dim() == 0 { + write!(f, "[]").unwrap(); + return result; + } + // find largest width + let mut width = 0; + let mut buf = String::new(); + for i in 0..u.dim() { + write!(&mut buf, "{:.1$e}", u[i], precision).unwrap(); + let _ = buf.split_off(buf.find('e').unwrap()); + width = cmp::max(buf.chars().count(), width); + buf.clear(); + } + width += 4; + // draw vector + width += 1; + write!(f, "┌{:1$}┐\n", " ", width + 1).unwrap(); + for i in 0..u.dim() { + if i > 0 { + write!(f, " │\n").unwrap(); + } + write!(f, "│").unwrap(); + write!(f, "{}", format_scientific(u[i], width, precision)).unwrap(); + } + write!(f, " │\n").unwrap(); + write!(f, "└{:1$}┘", " ", width + 1).unwrap(); + result +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::vec_fmt_scientific; + use crate::Vector; + + #[test] + fn vec_fmt_scientific_works() { + let u = Vector::from(&[1.012444, 200.034123, 1e-8]); + println!("{}", vec_fmt_scientific(&u, 3)); + assert_eq!( + vec_fmt_scientific(&u, 3), + "┌ ┐\n\ + │ 1.012E+00 │\n\ + │ 2.000E+02 │\n\ + │ 1.000E-08 │\n\ + └ ┘" + ); + + let u = Vector::from(&[1.012444, 200.034123, 1e+8]); + println!("{}", vec_fmt_scientific(&u, 4)); + assert_eq!( + vec_fmt_scientific(&u, 4), + "┌ ┐\n\ + │ 1.0124E+00 │\n\ + │ 2.0003E+02 │\n\ + │ 1.0000E+08 │\n\ + └ ┘" + ); + } +} From aee0b28ba5cefe3aa828bcfbe3e076b4abb91614 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 20:15:18 +1000 Subject: [PATCH 83/93] Fix refine function calling --- russell_lab/README.md | 2 +- russell_lab/examples/algo_multi_root_solver_cheby.rs | 2 +- russell_lab/src/algo/root_finding.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/russell_lab/README.md b/russell_lab/README.md index 3aff616a..b05c2c04 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -482,7 +482,7 @@ Total computation time = 907ns ### Finding all roots in an interval -This example employs a Chebyshev interpolant to find all roots of a function in an interval. The method uses adaptive interpolation followed by calculating the eigenvalues of the companion matrix. These eigenvalues equal the roots of the polynomial. After that, a simple Newton polishing algorithm is applied. +This example employs a Chebyshev interpolant to find all roots of a function in an interval. The method uses adaptive interpolation followed by calculating the eigenvalues of the companion matrix. These eigenvalues equal the roots of the polynomial. After that, a simple Newton refining (polishing) algorithm is applied. [See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_multi_root_solver_cheby.rs) diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_multi_root_solver_cheby.rs index ecd0d60f..f58b1ceb 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_multi_root_solver_cheby.rs @@ -23,7 +23,7 @@ fn main() -> Result<(), StrError> { println!("roots =\n{}", roots); println!("f @ roots =\n{}", vec_fmt_scientific(&f_at_roots, 2)); - // polish the roots + // refine/polish the roots let mut roots_refined = roots.clone(); solver.refine(roots_refined.as_mut_data(), xa, xb, args, f)?; let f_at_roots_refined = roots_refined.get_mapped(|x| f(x, args).unwrap()); diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index c75677c7..87a28994 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -53,7 +53,7 @@ pub struct RootFinding { /// Default = 1e-13 pub newton_tol_zero_fx: f64, - /// Holds the maximum number of iterations for the Newton polishing + /// Holds the maximum number of iterations for the Newton refinement/polishing /// /// Default = 15 pub newton_max_iterations: usize, @@ -218,8 +218,8 @@ impl RootFinding { /// let mut roots = solver.chebyshev(&interp)?; /// array_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate /// - /// // polish the roots - /// solver.polish_roots_newton(&mut roots, xa, xb, args, f)?; + /// // refine/polish the roots + /// solver.refine(&mut roots, xa, xb, args, f)?; /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); // accurate /// Ok(()) /// } @@ -428,7 +428,7 @@ mod tests { } #[test] - fn polish_roots_newton_captures_errors() { + fn refine_captures_errors() { let f = |_, _: &mut NoArgs| Ok(0.0); let args = &mut 0; let _ = f(0.0, args); @@ -448,7 +448,7 @@ mod tests { } #[test] - fn polish_roots_newton_works() { + fn refine_works() { // function let f = |x, _: &mut NoArgs| Ok(x * x * x * x - 1.0); let (xa, xb) = (-2.0, 2.0); @@ -471,7 +471,7 @@ mod tests { // figure /* graph( - "test_polish_roots_newton", + "test_root_finding_refine", &interp, &roots, &roots_refined, From 398dfe07cb5baa1e85522a6a8613b6eb27b235fb Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 20:18:55 +1000 Subject: [PATCH 84/93] Rename figures --- ...lver_cheby_002.svg => test_root_finding_chebyshev_002.svg} | 0 ...lver_cheby_003.svg => test_root_finding_chebyshev_003.svg} | 0 ...lver_cheby_004.svg => test_root_finding_chebyshev_004.svg} | 0 ...lver_cheby_005.svg => test_root_finding_chebyshev_005.svg} | 0 ...lver_cheby_006.svg => test_root_finding_chebyshev_006.svg} | 0 ...lver_cheby_007.svg => test_root_finding_chebyshev_007.svg} | 0 ...lver_cheby_008.svg => test_root_finding_chebyshev_008.svg} | 0 ...lver_cheby_009.svg => test_root_finding_chebyshev_009.svg} | 0 ...lver_cheby_010.svg => test_root_finding_chebyshev_010.svg} | 0 ...lver_cheby_013.svg => test_root_finding_chebyshev_013.svg} | 0 ...on.svg => test_root_finding_chebyshev_linear_function.svg} | 0 ...heby_simple.svg => test_root_finding_chebyshev_simple.svg} | 0 ...t_polish_roots_newton.svg => test_root_finding_refine.svg} | 0 russell_lab/src/algo/root_finding.rs | 4 ++-- 14 files changed, 2 insertions(+), 2 deletions(-) rename russell_lab/data/figures/{test_multi_root_solver_cheby_002.svg => test_root_finding_chebyshev_002.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_003.svg => test_root_finding_chebyshev_003.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_004.svg => test_root_finding_chebyshev_004.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_005.svg => test_root_finding_chebyshev_005.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_006.svg => test_root_finding_chebyshev_006.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_007.svg => test_root_finding_chebyshev_007.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_008.svg => test_root_finding_chebyshev_008.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_009.svg => test_root_finding_chebyshev_009.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_010.svg => test_root_finding_chebyshev_010.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_013.svg => test_root_finding_chebyshev_013.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_linear_function.svg => test_root_finding_chebyshev_linear_function.svg} (100%) rename russell_lab/data/figures/{test_multi_root_solver_cheby_simple.svg => test_root_finding_chebyshev_simple.svg} (100%) rename russell_lab/data/figures/{test_polish_roots_newton.svg => test_root_finding_refine.svg} (100%) diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_002.svg b/russell_lab/data/figures/test_root_finding_chebyshev_002.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_002.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_002.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_003.svg b/russell_lab/data/figures/test_root_finding_chebyshev_003.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_003.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_003.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_004.svg b/russell_lab/data/figures/test_root_finding_chebyshev_004.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_004.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_004.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_005.svg b/russell_lab/data/figures/test_root_finding_chebyshev_005.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_005.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_005.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_006.svg b/russell_lab/data/figures/test_root_finding_chebyshev_006.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_006.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_006.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_007.svg b/russell_lab/data/figures/test_root_finding_chebyshev_007.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_007.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_007.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_008.svg b/russell_lab/data/figures/test_root_finding_chebyshev_008.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_008.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_008.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_009.svg b/russell_lab/data/figures/test_root_finding_chebyshev_009.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_009.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_009.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_010.svg b/russell_lab/data/figures/test_root_finding_chebyshev_010.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_010.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_010.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_013.svg b/russell_lab/data/figures/test_root_finding_chebyshev_013.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_013.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_013.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_linear_function.svg b/russell_lab/data/figures/test_root_finding_chebyshev_linear_function.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_linear_function.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_linear_function.svg diff --git a/russell_lab/data/figures/test_multi_root_solver_cheby_simple.svg b/russell_lab/data/figures/test_root_finding_chebyshev_simple.svg similarity index 100% rename from russell_lab/data/figures/test_multi_root_solver_cheby_simple.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_simple.svg diff --git a/russell_lab/data/figures/test_polish_roots_newton.svg b/russell_lab/data/figures/test_root_finding_refine.svg similarity index 100% rename from russell_lab/data/figures/test_polish_roots_newton.svg rename to russell_lab/data/figures/test_root_finding_refine.svg diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index 87a28994..2ea1eb4e 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -530,7 +530,7 @@ mod tests { /* let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; graph( - &format!("test_multi_root_solver_cheby_{:0>3}", id), + &format!("test_root_finding_chebyshev_{:0>3}", id), &interp, &roots, &roots_refined, @@ -638,7 +638,7 @@ mod tests { .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") - .save("/tmp/russell_lab/test_multi_root_solver_cheby_linear_function.svg") + .save("/tmp/russell_lab/test_root_finding_chebyshev_linear_function.svg") .unwrap(); */ } From 81731e11021b14ec8419a7144966566ae1759170 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 20:20:57 +1000 Subject: [PATCH 85/93] Rename example --- russell_lab/README.md | 4 ++-- ..._root_solver_cheby.svg => algo_root_finding_chebyshev.svg} | 0 ...ti_root_solver_cheby.rs => algo_root_finding_chebyshev.rs} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename russell_lab/data/figures/{algo_multi_root_solver_cheby.svg => algo_root_finding_chebyshev.svg} (100%) rename russell_lab/examples/{algo_multi_root_solver_cheby.rs => algo_root_finding_chebyshev.rs} (97%) diff --git a/russell_lab/README.md b/russell_lab/README.md index b05c2c04..af88e9d5 100644 --- a/russell_lab/README.md +++ b/russell_lab/README.md @@ -484,7 +484,7 @@ Total computation time = 907ns This example employs a Chebyshev interpolant to find all roots of a function in an interval. The method uses adaptive interpolation followed by calculating the eigenvalues of the companion matrix. These eigenvalues equal the roots of the polynomial. After that, a simple Newton refining (polishing) algorithm is applied. -[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_multi_root_solver_cheby.rs) +[See the code](https://github.com/cpmech/russell/tree/main/russell_lab/examples/algo_root_finding_chebyshev.rs) The output looks like: @@ -530,7 +530,7 @@ f @ refined roots = The function and the roots are illustrated in the figure below. -![All roots in an interval](data/figures/algo_multi_root_solver_cheby.svg) +![All roots in an interval](data/figures/algo_root_finding_chebyshev.svg) **References** diff --git a/russell_lab/data/figures/algo_multi_root_solver_cheby.svg b/russell_lab/data/figures/algo_root_finding_chebyshev.svg similarity index 100% rename from russell_lab/data/figures/algo_multi_root_solver_cheby.svg rename to russell_lab/data/figures/algo_root_finding_chebyshev.svg diff --git a/russell_lab/examples/algo_multi_root_solver_cheby.rs b/russell_lab/examples/algo_root_finding_chebyshev.rs similarity index 97% rename from russell_lab/examples/algo_multi_root_solver_cheby.rs rename to russell_lab/examples/algo_root_finding_chebyshev.rs index f58b1ceb..28c0ac03 100644 --- a/russell_lab/examples/algo_multi_root_solver_cheby.rs +++ b/russell_lab/examples/algo_root_finding_chebyshev.rs @@ -76,7 +76,7 @@ fn main() -> Result<(), StrError> { .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") - .save("/tmp/russell_lab/algo_multi_root_solver_cheby.svg") + .save("/tmp/russell_lab/algo_root_finding_chebyshev.svg") .unwrap(); Ok(()) } From 22cb6eb50b816935378fe3e19d1e6829f89d785f Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 20:29:38 +1000 Subject: [PATCH 86/93] Improve doc comments --- russell_lab/src/algo/root_finding.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index 2ea1eb4e..a2a7c28d 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -99,13 +99,17 @@ impl RootFinding { /// * `interp` -- The Chebyshev-Gauss-Lobatto interpolant with the data vector U /// already computed. The interpolant must have the same degree N as this struct. /// - /// **Warning:** It is essential that the interpolant best approximates the - /// data/function; otherwise, not all roots can be found. - /// /// # Output /// /// Returns a sorted list (from xa to xb) with the roots. /// + /// # Warnings + /// + /// 1. It is essential that the interpolant best approximates the data/function; + /// otherwise, not all roots can be found. + /// 2. This function won't find roots with a multiplicity index greater than 1. + /// For example, this function won't find the roots of a parabola touching y = 0. + /// /// # Example /// /// ``` From fcf7b930a78ad706f508c15982602902ef413d8c Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 20:51:21 +1000 Subject: [PATCH 87/93] [Important] Use absolute tolerance in RootFinding. Enable finding roots with higher multiplicity --- ...t_root_finding_chebyshev_multiplicity2.svg | 1158 +++++++++++++++ ... test_root_finding_chebyshev_parabola.svg} | 0 ..._root_finding_chebyshev_parabola_mult2.svg | 1301 +++++++++++++++++ russell_lab/src/algo/root_finding.rs | 94 +- 4 files changed, 2545 insertions(+), 8 deletions(-) create mode 100644 russell_lab/data/figures/test_root_finding_chebyshev_multiplicity2.svg rename russell_lab/data/figures/{test_root_finding_chebyshev_simple.svg => test_root_finding_chebyshev_parabola.svg} (100%) create mode 100644 russell_lab/data/figures/test_root_finding_chebyshev_parabola_mult2.svg diff --git a/russell_lab/data/figures/test_root_finding_chebyshev_multiplicity2.svg b/russell_lab/data/figures/test_root_finding_chebyshev_multiplicity2.svg new file mode 100644 index 00000000..f198bec4 --- /dev/null +++ b/russell_lab/data/figures/test_root_finding_chebyshev_multiplicity2.svg @@ -0,0 +1,1158 @@ + + + + + + + + 2024-06-26T20:45:44.365090 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/data/figures/test_root_finding_chebyshev_simple.svg b/russell_lab/data/figures/test_root_finding_chebyshev_parabola.svg similarity index 100% rename from russell_lab/data/figures/test_root_finding_chebyshev_simple.svg rename to russell_lab/data/figures/test_root_finding_chebyshev_parabola.svg diff --git a/russell_lab/data/figures/test_root_finding_chebyshev_parabola_mult2.svg b/russell_lab/data/figures/test_root_finding_chebyshev_parabola_mult2.svg new file mode 100644 index 00000000..ddace787 --- /dev/null +++ b/russell_lab/data/figures/test_root_finding_chebyshev_parabola_mult2.svg @@ -0,0 +1,1301 @@ + + + + + + + + 2024-06-26T20:45:42.208282 + image/svg+xml + + + Matplotlib v3.6.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index a2a7c28d..43bfacb4 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -29,10 +29,10 @@ pub struct RootFinding { /// Holds the tolerance to discard roots with imaginary part /// - /// Accepts only roots such that `abs(Im(root)) < tol_rel_imag * abs(Re(root))` + /// Accepts only roots such that `abs(Im(root)) < tol_abs_imaginary /// /// Default = 1e-8 - pub tol_rel_imag: f64, + pub tol_abs_imaginary: f64, /// Holds the tolerance to discard roots outside the boundaries /// @@ -80,7 +80,7 @@ impl RootFinding { pub fn new() -> Self { RootFinding { tol_zero_an: 1e-13, - tol_rel_imag: 1.0e-8, + tol_abs_imaginary: 1.0e-8, tol_abs_boundary: TOL_RANGE / 10.0, newton_tol_zero_dx: 1e-13, newton_tol_zero_fx: 1e-13, @@ -107,8 +107,6 @@ impl RootFinding { /// /// 1. It is essential that the interpolant best approximates the data/function; /// otherwise, not all roots can be found. - /// 2. This function won't find roots with a multiplicity index greater than 1. - /// For example, this function won't find the roots of a parabola touching y = 0. /// /// # Example /// @@ -184,7 +182,7 @@ impl RootFinding { // roots = real eigenvalues within the interval let mut roots = Vec::new(); for i in 0..nn { - if f64::abs(l_imag[i]) < self.tol_rel_imag * f64::abs(l_real[i]) { + if f64::abs(l_imag[i]) < self.tol_abs_imaginary { if f64::abs(l_real[i]) <= 1.0 + self.tol_abs_boundary { let x = (xb + xa + dx * l_real[i]) / 2.0; roots.push(f64::max(xa, f64::min(xb, x))); @@ -393,7 +391,7 @@ mod tests { } #[test] - fn find_works_simple() { + fn find_works_parabola() { // function let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); let (xa, xb) = (-4.0, 4.0); @@ -416,7 +414,7 @@ mod tests { // figure /* graph( - "test_multi_root_solver_cheby_simple", + "test_root_finding_chebyshev_parabola", &interp, &roots, &roots_refined, @@ -431,6 +429,86 @@ mod tests { array_approx_eq(&roots_refined, &[-1.0, 1.0], 1e-14); } + #[test] + fn find_works_parabola_mult2() { + // solution: x = 0.0 with multiplicity 2 + + // function + let f = |x, _: &mut NoArgs| Ok(x * x); + let (xa, xb) = (-4.0, 4.0); + + // interpolant + let nn = 2; + let args = &mut 0; + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); + + // find roots + let solver = RootFinding::new(); + let roots = solver.chebyshev(&interp).unwrap(); + let mut roots_refined = roots.clone(); + solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); + println!("n_roots = {}", roots_refined.len()); + println!("roots = {:?}", roots); + println!("roots_refined = {:?}", roots_refined); + + // figure + /* + graph( + "test_root_finding_chebyshev_parabola_mult2", + &interp, + &roots, + &roots_refined, + args, + f, + 101, + 600.0, + ); + */ + + // check + array_approx_eq(&roots_refined, &[0.0, 0.0], 1e-14); + } + + #[test] + fn find_works_multiplicity2() { + // function + let f = |x, _: &mut NoArgs| Ok((x + 4.0) * (x - 1.0) * (x - 1.0)); + let (xa, xb) = (-5.0, 5.0); + + // interpolant + let nn = 3; + let args = &mut 0; + let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); + interp.set_function(nn, args, f).unwrap(); + + // find roots + let solver = RootFinding::new(); + let roots = solver.chebyshev(&interp).unwrap(); + let mut roots_refined = roots.clone(); + solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); + println!("n_roots = {}", roots_refined.len()); + println!("roots = {:?}", roots); + println!("roots_refined = {:?}", roots_refined); + + // figure + /* + graph( + "test_root_finding_chebyshev_multiplicity2", + &interp, + &roots, + &roots_refined, + args, + f, + 101, + 600.0, + ); + */ + + // check + array_approx_eq(&roots_refined, &[-4.0, 1.0, 1.0], 1e-14); + } + #[test] fn refine_captures_errors() { let f = |_, _: &mut NoArgs| Ok(0.0); From 2dc684a6bbba583a6cba41fc6b371f43ae3ad284 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 21:21:51 +1000 Subject: [PATCH 88/93] Improve root finding test --- russell_lab/src/algo/root_finding.rs | 19 ++++++----- russell_lab/src/algo/testing.rs | 51 +++++++++++++++++++--------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finding.rs index 43bfacb4..910b7765 100644 --- a/russell_lab/src/algo/root_finding.rs +++ b/russell_lab/src/algo/root_finding.rs @@ -573,9 +573,10 @@ mod tests { let nn_max = 200; let tol = 1e-8; let args = &mut 0; - let tests = get_test_functions(); - for id in &[2, 3, 4, 5, 6, 7, 8, 9, 10, 13] { - let test = &tests[*id]; + for test in get_test_functions() { + if test.id == 0 { + continue; + } println!("\n==================================================================="); println!("\n{}", test.name); let (xa, xb) = test.range; @@ -584,7 +585,9 @@ mod tests { let solver = RootFinding::new(); let roots = solver.chebyshev(&interp).unwrap(); let mut roots_refined = roots.clone(); - solver.refine(&mut roots_refined, xa, xb, args, test.f).unwrap(); + if roots.len() > 0 { + solver.refine(&mut roots_refined, xa, xb, args, test.f).unwrap(); + } for xr in &roots_refined { let fx = (test.f)(*xr, args).unwrap(); println!("x = {}, f(x) = {:.2e}", xr, fx); @@ -605,14 +608,12 @@ mod tests { } } } - if *id == 9 { - assert_eq!(roots.len(), 93); - } + assert_eq!(roots.len(), test.nroot); // figure /* - let (nstation, fig_width) = if *id == 9 { (1001, 2048.0) } else { (101, 600.0) }; + let (nstation, fig_width) = if test.id == 9 { (1001, 2048.0) } else { (101, 600.0) }; graph( - &format!("test_root_finding_chebyshev_{:0>3}", id), + &format!("test_root_finding_chebyshev_{:0>3}", test.id), &interp, &roots, &roots_refined, diff --git a/russell_lab/src/algo/testing.rs b/russell_lab/src/algo/testing.rs index 91bbe8d7..7960d046 100644 --- a/russell_lab/src/algo/testing.rs +++ b/russell_lab/src/algo/testing.rs @@ -4,6 +4,9 @@ use crate::StrError; /// Holds an f(x) function that is useful for testing pub struct TestFunction { + /// Holds the identifier of the function + pub id: usize, + /// Holds the name of the function pub name: &'static str, @@ -23,7 +26,7 @@ pub struct TestFunction { pub range: (f64, f64), /// Holds the number of roots in the specified range - pub n_root: usize, + pub nroot: usize, /// Holds a bracketed local minimum pub min1: Option, @@ -92,12 +95,13 @@ pub struct TestFunction { pub fn get_test_functions() -> Vec { vec![ TestFunction { + id: 0, name: "0: f(x) = undefined", f: |_, _| Err("stop"), g: |_, _| Err("stop"), h: |_, _| Err("stop"), range: (-5.0, 5.0), - n_root: 0, + nroot: 0, min1: None, min2: None, min3: None, @@ -110,12 +114,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-10, }, TestFunction { + id: 1, name: "1: f(x) = x² - 1", f: |x, _| Ok(x * x - 1.0), g: |x, _| Ok(2.0 * x), h: |_, _| Ok(2.0), range: (-5.0, 5.0), - n_root: 2, + nroot: 2, min1: Some(Bracket { a: -5.0, b: 5.0, @@ -149,6 +154,7 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-14, }, TestFunction { + id: 2, name: "2: f(x) = 1/2 - 1/(1 + 16 x²)", // (shifted) Runge equation f: |x, _| Ok(1.0 / 2.0 - 1.0 / (1.0 + 16.0 * x * x)), g: |x, _| Ok((32.0 * x) / f64::powi(1.0 + 16.0 * f64::powi(x, 2), 2)), @@ -157,7 +163,7 @@ pub fn get_test_functions() -> Vec { + 32.0 / f64::powi(1.0 + 16.0 * f64::powi(x, 2), 2)) }, range: (-2.0, 2.0), - n_root: 2, + nroot: 2, min1: Some(Bracket { a: -2.0, b: 2.0, @@ -191,12 +197,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-13, }, TestFunction { + id: 3, name: "3: f(x) = x⁵ + 3x⁴ - 2x³ + x - 1", f: |x, _| Ok(f64::powi(x, 5) + 3.0 * f64::powi(x, 4) - 2.0 * f64::powi(x, 3) + x - 1.0), g: |x, _| Ok(1.0 - 6.0 * f64::powi(x, 2) + 12.0 * f64::powi(x, 3) + 5.0 * f64::powi(x, 4)), h: |x, _| Ok(-12.0 * x + 36.0 * f64::powi(x, 2) + 20.0 * f64::powi(x, 3)), range: (-3.6, 2.0), - n_root: 3, + nroot: 3, min1: Some(Bracket { a: -2.0, b: 2.0, @@ -237,12 +244,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-13, }, TestFunction { + id: 4, name: "4: f(x) = (x - 1)² + 5 sin(x)", f: |x, _| Ok(f64::powi(x - 1.0, 2) + 5.0 * f64::sin(x)), g: |x, _| Ok(2.0 * (-1.0 + x) + 5.0 * f64::cos(x)), h: |x, _| Ok(2.0 - 5.0 * f64::sin(x)), range: (-2.8, 5.0), - n_root: 2, + nroot: 2, min1: Some(Bracket { a: -2.0, b: 2.0, @@ -283,6 +291,7 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-14, }, TestFunction { + id: 5, name: "5: f(x) = 1/(1 - exp(-2 x) sin²(5 π x)) - 3/2", f: |x, _| Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5), g: |x, _| { @@ -308,7 +317,7 @@ pub fn get_test_functions() -> Vec { ) }, range: (0.0, 1.0), - n_root: 6, + nroot: 6, min1: Some(Bracket { a: 0.1, b: 0.3, @@ -363,12 +372,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-14, }, TestFunction { + id: 6, name: "6: f(x) = sin(x) in [0, π]", f: |x, _| Ok(f64::sin(x)), g: |x, _| Ok(f64::cos(x)), h: |x, _| Ok(-f64::sin(x)), range: (0.0, PI), - n_root: 2, + nroot: 2, min1: None, min2: None, min3: None, @@ -381,12 +391,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-15, }, TestFunction { + id: 7, name: "7: f(x) = sin(x) in [0, π/2]", f: |x, _| Ok(f64::sin(x)), g: |x, _| Ok(f64::cos(x)), h: |x, _| Ok(-f64::sin(x)), range: (0.0, PI / 2.0), - n_root: 1, + nroot: 1, min1: None, min2: None, min3: None, @@ -399,12 +410,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-15, }, TestFunction { + id: 8, name: "8: f(x) = sin(x) in [-1, 1]", f: |x, _| Ok(f64::sin(x)), g: |x, _| Ok(f64::cos(x)), h: |x, _| Ok(-f64::sin(x)), range: (-1.0, 1.0), - n_root: 1, + nroot: 1, min1: None, min2: None, min3: None, @@ -424,12 +436,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-20, }, TestFunction { + id: 9, name: "9: f(x) = 0.092834 sin(77.0001 + 19.87 x) in [-2.34567, 12.34567]", f: |x, _| Ok(0.092834 * f64::sin(77.0001 + 19.87 * x)), g: |x, _| Ok(1.84461158 * f64::cos(77.0001 + 19.87 * x)), h: |x, _| Ok(-36.6524320946 * f64::sin(77.0001 + 19.87 * x)), range: (-2.34567, 12.34567), - n_root: 93, + nroot: 93, min1: None, min2: None, min3: None, @@ -442,12 +455,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-15, }, TestFunction { + id: 10, name: "10: f(x) = 0.092834 sin[7.0001 + 1.87 x) in [-2.34567, 1.34567]", f: |x, _| Ok(0.092834 * f64::sin(7.0001 + 1.87 * x)), g: |x, _| Ok(0.17359958 * f64::cos(7.0001 + 1.87 * x)), h: |x, _| Ok(-0.32463121460000005 * f64::sin(7.0001 + 1.87 * x)), range: (-2.5, 1.5), - n_root: 3, + nroot: 3, min1: Some(Bracket { a: -2.0, b: 1.0, @@ -488,6 +502,7 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-16, }, TestFunction { + id: 11, name: "11: f(x) = (2 x⁵ - x + 3)/x²", f: |x, _| Ok((2.0 * f64::powi(x, 5) - x + 3.0) / (x * x)), g: |x, _| { @@ -499,7 +514,7 @@ pub fn get_test_functions() -> Vec { + (6.0 * (3.0 - x + 2.0 * f64::powi(x, 5))) / f64::powi(x, 4)) }, range: (1.0, 2.0), - n_root: 0, + nroot: 0, min1: None, min2: None, min3: None, @@ -512,12 +527,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-13, }, TestFunction { + id: 12, name: "12: f(x) = 3/exp(-x) - 1/(3x)", f: |x, _| Ok(3.0 / f64::exp(-x) - 1.0 / (3.0 * x)), g: |x, _| Ok(3.0 * f64::exp(x) + 1.0 / (3.0 * f64::powi(x, 2))), h: |x, _| Ok(3.0 * f64::exp(x) - 2.0 / (3.0 * f64::powi(x, 3))), range: (-20.0, -1.0), - n_root: 0, + nroot: 0, min1: None, min2: None, min3: None, @@ -530,12 +546,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-14, }, TestFunction { + id: 13, name: "13: f(x) = log(2 f64::cos(x/2))", f: |x, _| Ok(f64::ln(2.0 * f64::cos(x / 2.0))), g: |x, _| Ok(-0.5 * f64::tan(x / 2.0)), h: |x, _| Ok(-0.25 * f64::powi(1.0 / f64::cos(x / 2.0), 2)), range: (-0.995 * PI, 0.995 * PI), - n_root: 2, + nroot: 2, min1: None, min2: None, min3: None, @@ -562,12 +579,13 @@ pub fn get_test_functions() -> Vec { tol_integral: 1e-10, }, TestFunction { + id: 14, name: "14: f(x) = exp(x)", f: |x, _| Ok(f64::exp(x)), g: |x, _| Ok(f64::exp(x)), h: |x, _| Ok(f64::exp(x)), range: (0.0, 10.1), - n_root: 0, + nroot: 0, min1: None, min2: None, min3: None, @@ -610,6 +628,7 @@ mod tests { let args = &mut 0; for (i, func) in get_test_functions().iter().enumerate() { println!("\n{}", func.name); + assert_eq!(i, func.id); if i == 0 { assert_eq!((func.f)(0.0, args).err(), Some("stop")); assert_eq!((func.g)(0.0, args).err(), Some("stop")); From e2d9ce658a7d548e0ce28e2c4ea9f5d4a0ffe963 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 21:25:03 +1000 Subject: [PATCH 89/93] Rename root_finder files --- russell_lab/src/algo/mod.rs | 6 +++--- russell_lab/src/algo/{root_finding.rs => root_finder.rs} | 0 .../algo/{root_finding_brent.rs => root_finder_brent.rs} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename russell_lab/src/algo/{root_finding.rs => root_finder.rs} (100%) rename russell_lab/src/algo/{root_finding_brent.rs => root_finder_brent.rs} (100%) diff --git a/russell_lab/src/algo/mod.rs b/russell_lab/src/algo/mod.rs index 36065287..bcabbccc 100644 --- a/russell_lab/src/algo/mod.rs +++ b/russell_lab/src/algo/mod.rs @@ -8,8 +8,8 @@ mod min_bracketing; mod min_solver; mod num_jacobian; mod quadrature; -mod root_finding; -mod root_finding_brent; +mod root_finder; +mod root_finder_brent; mod testing; pub use crate::algo::common::*; pub use crate::algo::interp_chebyshev::*; @@ -19,5 +19,5 @@ pub use crate::algo::min_bracketing::*; pub use crate::algo::min_solver::*; pub use crate::algo::num_jacobian::*; pub use crate::algo::quadrature::*; -pub use crate::algo::root_finding::*; +pub use crate::algo::root_finder::*; pub use crate::algo::testing::*; diff --git a/russell_lab/src/algo/root_finding.rs b/russell_lab/src/algo/root_finder.rs similarity index 100% rename from russell_lab/src/algo/root_finding.rs rename to russell_lab/src/algo/root_finder.rs diff --git a/russell_lab/src/algo/root_finding_brent.rs b/russell_lab/src/algo/root_finder_brent.rs similarity index 100% rename from russell_lab/src/algo/root_finding_brent.rs rename to russell_lab/src/algo/root_finder_brent.rs From b8eb8a35b9a44feb6b1205c1298ee715eed4ca1a Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 21:29:33 +1000 Subject: [PATCH 90/93] Rename RootFinder --- .../algo_min_and_root_solver_brent.rs | 2 +- .../examples/algo_root_finding_chebyshev.rs | 2 +- russell_lab/src/algo/root_finder.rs | 68 +++++++++---------- russell_lab/src/algo/root_finder_brent.rs | 14 ++-- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/russell_lab/examples/algo_min_and_root_solver_brent.rs b/russell_lab/examples/algo_min_and_root_solver_brent.rs index 351b2ab8..4456c37e 100644 --- a/russell_lab/examples/algo_min_and_root_solver_brent.rs +++ b/russell_lab/examples/algo_min_and_root_solver_brent.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), StrError> { println!("\n{}", stats); // root - let solver = RootFinding::new(); + let solver = RootFinder::new(); let (xo, stats) = solver.brent(0.3, 0.4, args, |x, _| { Ok(1.0 / (1.0 - f64::exp(-2.0 * x) * f64::powi(f64::sin(5.0 * PI * x), 2)) - 1.5) })?; diff --git a/russell_lab/examples/algo_root_finding_chebyshev.rs b/russell_lab/examples/algo_root_finding_chebyshev.rs index 28c0ac03..ea7e60e1 100644 --- a/russell_lab/examples/algo_root_finding_chebyshev.rs +++ b/russell_lab/examples/algo_root_finding_chebyshev.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), StrError> { println!("N = {}", nn); // find all roots in the interval - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = Vector::from(&solver.chebyshev(&interp)?); let f_at_roots = roots.get_mapped(|x| f(x, args).unwrap()); println!("roots =\n{}", roots); diff --git a/russell_lab/src/algo/root_finder.rs b/russell_lab/src/algo/root_finder.rs index 910b7765..c4fc0dde 100644 --- a/russell_lab/src/algo/root_finder.rs +++ b/russell_lab/src/algo/root_finder.rs @@ -2,26 +2,8 @@ use crate::StrError; use crate::{mat_eigenvalues, InterpChebyshev, TOL_RANGE}; use crate::{Matrix, Vector}; -/// Implements a root finding solver using Chebyshev interpolation -/// -/// This struct depends on [InterpChebyshev], an interpolant using -/// Chebyshev-Gauss-Lobatto points. -/// -/// It is essential that the interpolant best approximates the -/// data/function; otherwise, not all roots can be found. -/// -/// The roots are the eigenvalues of the companion matrix. -/// -/// # References -/// -/// 1. Boyd JP (2002) Computing zeros on a real interval through Chebyshev expansion -/// and polynomial rootfinding, SIAM Journal of Numerical Analysis, 40(5):1666-1682 -/// 2. Boyd JP (2013) Finding the zeros of a univariate equation: proxy rootfinders, -/// Chebyshev interpolation, and the companion matrix, SIAM Journal of Numerical -/// Analysis, 55(2):375-396. -/// 3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy -/// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 -pub struct RootFinding { +/// Implements root finding algorithms +pub struct RootFinder { /// Holds the tolerance to avoid division by zero with the trailing Chebyshev coefficient /// /// Default = 1e-13 @@ -75,10 +57,10 @@ pub struct RootFinding { h_cen: f64, } -impl RootFinding { +impl RootFinder { /// Allocates a new instance pub fn new() -> Self { - RootFinding { + RootFinder { tol_zero_an: 1e-13, tol_abs_imaginary: 1.0e-8, tol_abs_boundary: TOL_RANGE / 10.0, @@ -108,6 +90,20 @@ impl RootFinding { /// 1. It is essential that the interpolant best approximates the data/function; /// otherwise, not all roots can be found. /// + /// # Method + /// + /// The roots are the eigenvalues of the companion matrix as explained in the references. + /// + /// # References + /// + /// 1. Boyd JP (2002) Computing zeros on a real interval through Chebyshev expansion + /// and polynomial rootfinding, SIAM Journal of Numerical Analysis, 40(5):1666-1682 + /// 2. Boyd JP (2013) Finding the zeros of a univariate equation: proxy rootfinders, + /// Chebyshev interpolation, and the companion matrix, SIAM Journal of Numerical + /// Analysis, 55(2):375-396. + /// 3. Boyd JP (2014) Solving Transcendental Equations: The Chebyshev Polynomial Proxy + /// and Other Numerical Rootfinders, Perturbation Series, and Oracles, SIAM, pp460 + /// /// # Example /// /// ``` @@ -125,7 +121,7 @@ impl RootFinding { /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval - /// let mut solver = RootFinding::new(); + /// let mut solver = RootFinder::new(); /// let roots = solver.chebyshev(&interp)?; /// array_approx_eq(&roots, &[-1.0, 1.0], 1e-15); /// Ok(()) @@ -216,7 +212,7 @@ impl RootFinding { /// interp.set_function(nn, args, f)?; /// /// // find all roots in the interval - /// let mut solver = RootFinding::new(); + /// let mut solver = RootFinder::new(); /// let mut roots = solver.chebyshev(&interp)?; /// array_approx_eq(&roots, &[-0.5, 0.5], 1e-15); // inaccurate /// @@ -288,7 +284,7 @@ impl RootFinding { #[cfg(test)] mod tests { - use super::RootFinding; + use super::RootFinder; use crate::algo::NoArgs; use crate::InterpChebyshev; use crate::{approx_eq, array_approx_eq, get_test_functions}; @@ -368,7 +364,7 @@ mod tests { let (xa, xb) = (-4.0, 4.0); let nn = 2; let interp = InterpChebyshev::new(nn, xa, xb).unwrap(); - let solver = RootFinding::new(); + let solver = RootFinder::new(); assert_eq!( solver.chebyshev(&interp).err(), Some("the interpolant must initialized first") @@ -383,7 +379,7 @@ mod tests { let args = &mut 0; let mut interp = InterpChebyshev::new(nn, xa, xb).unwrap(); interp.set_function(nn, args, f).unwrap(); - let solver = RootFinding::new(); + let solver = RootFinder::new(); assert_eq!( solver.chebyshev(&interp).err(), Some("the trailing Chebyshev coefficient vanishes; try a smaller degree N") @@ -403,7 +399,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = solver.chebyshev(&interp).unwrap(); let mut roots_refined = roots.clone(); solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); @@ -444,7 +440,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = solver.chebyshev(&interp).unwrap(); let mut roots_refined = roots.clone(); solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); @@ -483,7 +479,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = solver.chebyshev(&interp).unwrap(); let mut roots_refined = roots.clone(); solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); @@ -515,7 +511,7 @@ mod tests { let args = &mut 0; let _ = f(0.0, args); let (xa, xb) = (-1.0, 1.0); - let mut solver = RootFinding::new(); + let mut solver = RootFinder::new(); let mut roots = Vec::new(); assert_eq!( solver.refine(&mut roots, xa, xb, args, f).err(), @@ -542,7 +538,7 @@ mod tests { interp.set_function(nn, args, f).unwrap(); // find roots - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = solver.chebyshev(&interp).unwrap(); let mut roots_refined = roots.clone(); solver.refine(&mut roots_refined, xa, xb, args, f).unwrap(); @@ -582,7 +578,7 @@ mod tests { let (xa, xb) = test.range; let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); interp.adapt_function(tol, args, test.f).unwrap(); - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = solver.chebyshev(&interp).unwrap(); let mut roots_refined = roots.clone(); if roots.len() > 0 { @@ -638,7 +634,7 @@ mod tests { interp.set_data(uu).unwrap(); // find all roots in the interval - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = &solver.chebyshev(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) @@ -657,7 +653,7 @@ mod tests { interp.adapt_data(tol, uu).unwrap(); // find all roots in the interval - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = &solver.chebyshev(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 0) @@ -684,7 +680,7 @@ mod tests { interp.adapt_data(tol, uu).unwrap(); // find all roots in the interval - let solver = RootFinding::new(); + let solver = RootFinder::new(); let roots = solver.chebyshev(&interp).unwrap(); let nroot = roots.len(); assert_eq!(nroot, 1); diff --git a/russell_lab/src/algo/root_finder_brent.rs b/russell_lab/src/algo/root_finder_brent.rs index e0bbc9ba..9088f55e 100644 --- a/russell_lab/src/algo/root_finder_brent.rs +++ b/russell_lab/src/algo/root_finder_brent.rs @@ -1,7 +1,7 @@ -use super::{RootFinding, Stats}; +use super::{RootFinder, Stats}; use crate::StrError; -impl RootFinding { +impl RootFinder { /// Employs Brent's method to find a single root of an equation /// /// See: @@ -31,7 +31,7 @@ impl RootFinding { /// /// fn main() -> Result<(), StrError> { /// let args = &mut 0; - /// let solver = RootFinding::new(); + /// let solver = RootFinder::new(); /// let (xa, xb) = (-4.0, 0.0); /// let (xo, stats) = solver.brent(xa, xb, args, |x, _| Ok(4.0 - x * x))?; /// println!("\nroot = {:?}", xo); @@ -196,13 +196,13 @@ impl RootFinding { mod tests { use crate::algo::testing::get_test_functions; use crate::approx_eq; - use crate::{NoArgs, RootFinding}; + use crate::{NoArgs, RootFinder}; #[test] fn brent_find_captures_errors_1() { let f = |x, _: &mut NoArgs| Ok(x * x - 1.0); let args = &mut 0; - let mut solver = RootFinding::new(); + let mut solver = RootFinder::new(); assert_eq!(f(1.0, args).unwrap(), 0.0); assert_eq!( solver.brent(-0.5, -0.5, args, f).err(), @@ -235,7 +235,7 @@ mod tests { res }; let args = &mut Args { count: 0, target: 0 }; - let solver = RootFinding::new(); + let solver = RootFinder::new(); // first function call assert_eq!(solver.brent(-0.5, 2.0, args, f).err(), Some("stop")); // second function call @@ -251,7 +251,7 @@ mod tests { #[test] fn brent_find_works() { let args = &mut 0; - let solver = RootFinding::new(); + let solver = RootFinder::new(); for test in &get_test_functions() { println!("\n==================================================================="); println!("\n{}", test.name); From 7db4b37b538129a532bc7cb8a218ade1cd15bff5 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Wed, 26 Jun 2024 21:45:13 +1000 Subject: [PATCH 91/93] Increase tolerance for the imaginary part of roots. Improve multiplicity detection --- russell_lab/src/algo/root_finder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/russell_lab/src/algo/root_finder.rs b/russell_lab/src/algo/root_finder.rs index c4fc0dde..d288cabc 100644 --- a/russell_lab/src/algo/root_finder.rs +++ b/russell_lab/src/algo/root_finder.rs @@ -13,7 +13,7 @@ pub struct RootFinder { /// /// Accepts only roots such that `abs(Im(root)) < tol_abs_imaginary /// - /// Default = 1e-8 + /// Default = 1e-7 pub tol_abs_imaginary: f64, /// Holds the tolerance to discard roots outside the boundaries @@ -62,7 +62,7 @@ impl RootFinder { pub fn new() -> Self { RootFinder { tol_zero_an: 1e-13, - tol_abs_imaginary: 1.0e-8, + tol_abs_imaginary: 1.0e-7, tol_abs_boundary: TOL_RANGE / 10.0, newton_tol_zero_dx: 1e-13, newton_tol_zero_fx: 1e-13, From 6af0b05493657a759f493f037843180452779608 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Thu, 27 Jun 2024 15:34:18 +1000 Subject: [PATCH 92/93] Improve adaptive interpolation in InterpChebyshev with fixed number of data points --- russell_lab/src/algo/interp_chebyshev.rs | 77 ++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index 4b1725a6..b83b8120 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -114,6 +114,7 @@ impl InterpChebyshev { if xb <= xa + TOL_RANGE { return Err("xb must be greater than xa + ϵ"); } + let nn_max = nn_max + 2; // add 2 because adapt_function subtracts 2 in the end let np_max = nn_max + 1; Ok(InterpChebyshev { nn_max, @@ -521,6 +522,7 @@ mod tests { let nn_max = 2; let (xa, xb) = (-4.0, 4.0); let interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + let nn_max = nn_max + 2; // for adapt_function let np_max = nn_max + 1; assert_eq!(interp.nn_max, nn_max); assert_eq!(interp.nn, 0); @@ -539,7 +541,7 @@ mod tests { let args = &mut 0; _ = f(0.0, args); // for coverage tool let mut interp = InterpChebyshev::new(2, 0.0, 1.0).unwrap(); - assert_eq!(interp.set_function(3, args, f).err(), Some("nn must be ≤ nn_max")); + assert_eq!(interp.set_function(5, args, f).err(), Some("nn must be ≤ nn_max")); let f = |_: f64, _: &mut NoArgs| Err("stop"); assert_eq!(interp.set_function(0, args, f).err(), Some("stop")); assert_eq!(interp.set_function(1, args, f).err(), Some("stop")); @@ -572,7 +574,7 @@ mod tests { interp.set_data(uu.as_data()).err(), Some("the number of points (uu.len()) must be ≥ 1") ); - let uu = Vector::new(3); + let uu = Vector::new(5); assert_eq!( interp.set_data(uu.as_data()).err(), Some("nn (=uu.len()-1) must be ≤ nn_max") @@ -804,7 +806,7 @@ mod tests { interp.adapt_data(1e-7, &uu).err(), Some("the number of points (uu.len()) must be ≥ 1") ); - let uu = [1.0, 2.0, 3.0]; + let uu = [1.0, 2.0, 3.0, 4.0, 5.0]; assert_eq!(interp.adapt_data(1e-7, &uu).err(), Some("nn must be ≤ nn_max")); } @@ -1082,6 +1084,73 @@ mod tests { assert_eq!(interp.get_degree(), 2); assert_eq!(interp.get_range(), (-4.0, 4.0, 8.0)); assert_eq!(interp.is_ready(), true); - array_approx_eq(interp.get_coefficients(), &[7.0, 0.0, 8.0], 1e-15); + array_approx_eq(interp.get_coefficients(), &[7.0, 0.0, 8.0, 0.0, 0.0], 1e-15); + } + + #[test] + fn case_with_discontinuity_1() { + let uu = [ + -1.5000000000000009, + -1.5410857847379518, + -1.6638929944964582, + -1.8670761277863486, + -2.1484090676804932, + -2.504809471616711, + -2.9323725421878937, + -3.426413808919543, + -3.9815204523085637, + -4.591610607806451, + -5.249999999999998, + -5.9494751769314975, + -6.682372542187893, + -7.4406623188668055, + -8.216036525492598, + -8.999999999999998, + -8.2160365254926, + -7.440662318866806, + -6.682372542187896, + -5.949475176931499, + -5.250000000000002, + -4.591610607806453, + -3.9815204523085654, + -3.4264138089195466, + -2.9323725421878954, + -2.5048094716167135, + -2.1484090676804986, + -1.8670761277863521, + -1.6638929944964609, + -1.5410857847379535, + -1.5000000000000044, + ]; + let (xa, xb) = (0.0, 1.0); + let nn_max = 30; + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + interp.adapt_data(1e-8, &uu).unwrap(); + let nn = interp.get_degree(); + assert_eq!(nn, 30); + + // plot + /* + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_int = Curve::new(); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/test_interp_chebyshev_case_with_discontinuity_1.svg") + .unwrap(); + */ } } From 369d5bb89f1b57694c4195a9571f3935e8103810 Mon Sep 17 00:00:00 2001 From: Dorival Pedroso Date: Thu, 27 Jun 2024 17:55:16 +1000 Subject: [PATCH 93/93] Add test --- russell_lab/src/algo/interp_chebyshev.rs | 44 +++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/russell_lab/src/algo/interp_chebyshev.rs b/russell_lab/src/algo/interp_chebyshev.rs index b83b8120..4d01fb25 100644 --- a/russell_lab/src/algo/interp_chebyshev.rs +++ b/russell_lab/src/algo/interp_chebyshev.rs @@ -1089,6 +1089,48 @@ mod tests { #[test] fn case_with_discontinuity_1() { + let f = |x: f64, _: &mut NoArgs| { + if x < 0.0 { + Ok(-x) + } else { + Ok(x) + } + }; + let (xa, xb) = (-1.0, 1.0); + let nn_max = 200; + let mut interp = InterpChebyshev::new(nn_max, xa, xb).unwrap(); + let args = &mut 0; + interp.adapt_function(1e-4, args, f).unwrap(); + let nn = interp.get_degree(); + assert_eq!(nn, 124); + + // plot + /* + let xx = Vector::linspace(xa, xb, 201).unwrap(); + let yy_int = xx.get_mapped(|x| interp.eval(x).unwrap()); + let mut curve_int = Curve::new(); + curve_int + .set_label(&format!("interpolated,N={}", nn)) + .set_line_style(":") + .set_marker_style(".") + .set_marker_every(5); + curve_int.draw(xx.as_data(), yy_int.as_data()); + let mut plot = Plot::new(); + let mut legend = Legend::new(); + legend.set_num_col(4); + legend.set_outside(true); + legend.draw(); + plot.add(&curve_int) + .add(&legend) + .set_cross(0.0, 0.0, "gray", "-", 1.5) + .grid_and_labels("x", "f(x)") + .save("/tmp/russell_lab/test_interp_chebyshev_case_with_discontinuity_1.svg") + .unwrap(); + */ + } + + #[test] + fn case_with_discontinuity_2() { let uu = [ -1.5000000000000009, -1.5410857847379518, @@ -1149,7 +1191,7 @@ mod tests { .add(&legend) .set_cross(0.0, 0.0, "gray", "-", 1.5) .grid_and_labels("x", "f(x)") - .save("/tmp/russell_lab/test_interp_chebyshev_case_with_discontinuity_1.svg") + .save("/tmp/russell_lab/test_interp_chebyshev_case_with_discontinuity_2.svg") .unwrap(); */ }