roots-0.0.8/.cargo_vcs_info.json0000644000000001360000000000100122070ustar { "git": { "sha1": "3caf9740f66aa213468136dea7a59d04a33cc0a7" }, "path_in_vcs": "" }roots-0.0.8/.gitignore000064400000000000000000000002130072674642500130130ustar 00000000000000# Compiled files *.o *.so *.rlib *.dll # Executables *.exe # Generated by Cargo Cargo.lock target/ # VS Code .vscode/ *.rs.bk *.rs.orig roots-0.0.8/.travis.yml000064400000000000000000000002600072674642500131360ustar 00000000000000language: rust sudo: false script: - rustc --version - cargo test --verbose - cargo bench --verbose - cargo doc notifications: email: on_success: never roots-0.0.8/CHANGELOG.md000064400000000000000000000042020072674642500126360ustar 00000000000000# Change Log All notable changes to this project will be documented in this file. This project is going to adhere to [Semantic Versioning](http://semver.org/) after version 1.0.0. ## [Unreleased] ## [0.0.8] - 2022-12-21 * Remove debug print - thanks to J-F-Liu, Ralith * Fixed discriminant check in quartic equations - thanks to jingnanshi * Allowed mutable closures for secant etc. - thanks to vron ## [0.0.7] - 2021-06-17 * SearchError becomes public - thanks to JP-Ellis * Unnormalized cubic equations are solved using the general formula rather than trigonometrically - thanks to Logicalshift ## [0.0.6] - 2019-12-22 * Fixed cubic equations with very small a3 - thanks to Andrew Hunter * Improved quartic equations with multiple roots (for f64; f32 is still a problem) - thanks to Tim Lueke * Removed warnings of rustc 1.40.0 * Switched benchmarks from Bencher to Criterion ## [0.0.5] - 2019-01-20 * Trait Error implemented for SearchError - thanks to phillyfan1138 * Find roots of higher-degree polynomials using eigenvalues - thanks to stiv-yakovenko * Find roots of higher-degree polynomials using Sturm's theorem recursively (experimental) * Inverse quadratic approximation ## [0.0.4] - 2017-09-05 * Reduced the performance overhead by using generics - thanks to aepsil0n * Handle special cases of quadratic equations - thanks to stiv-yakovenko ## [0.0.3] - 2017-03-28 * New version of the compiler * Benchmarks * Improved the speed of the Brent-Dekker method * Reduced the convergency boilerplate ## [0.0.2] - 2015-06-08 * Fight against the compiler ## [0.0.1] - 2015-03-24 * Initial version [Unreleased]: https://github.com/vorot/roots/compare/v0.0.8...HEAD [0.0.8]: https://github.com/vorot/roots/compare/v0.0.7...v0.0.8 [0.0.7]: https://github.com/vorot/roots/compare/v0.0.6...v0.0.7 [0.0.6]: https://github.com/vorot/roots/compare/v0.0.5...v0.0.6 [0.0.5]: https://github.com/vorot/roots/compare/v0.0.4...v0.0.5 [0.0.4]: https://github.com/vorot/roots/compare/v0.0.3...v0.0.4 [0.0.3]: https://github.com/vorot/roots/compare/v0.0.2...v0.0.3 [0.0.2]: https://github.com/vorot/roots/compare/v0.0.1...v0.0.2 roots-0.0.8/Cargo.toml0000644000000017720000000000100102140ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "roots" version = "0.0.8" authors = ["Mikhail Vorotilov "] description = "Library of well known algorithms for numerical root finding.\n" readme = "README.md" keywords = ["math", "root", "numerical", "cubic", "quartic"] license = "BSD-2-Clause" repository = "https://github.com/vorot/roots" [lib] name = "roots" path = "src/lib.rs" [[bench]] name = "benches" harness = false [dev-dependencies.criterion] version = "0.3" [badges.travis-ci] branch = "master" repository = "https://github.com/vorot/roots" roots-0.0.8/Cargo.toml.orig0000644000000012270000000000100111460ustar [package] name = "roots" version = "0.0.8" authors = ["Mikhail Vorotilov "] license = "BSD-2-Clause" repository = "https://github.com/vorot/roots" description = """ Library of well known algorithms for numerical root finding. """ readme = "README.md" keywords = [ "math", "root", "numerical", "cubic", "quartic" ] [badges] # Travis CI: `repository` is required. `branch` is optional; default is `master` travis-ci = { repository = "https://github.com/vorot/roots", branch = "master" } [lib] name = "roots" path = "src/lib.rs" [dev-dependencies] criterion = "0.3" [[bench]] name="benches" harness=false roots-0.0.8/Cargo.toml.orig000064400000000000000000000012270072674642500137200ustar 00000000000000[package] name = "roots" version = "0.0.8" authors = ["Mikhail Vorotilov "] license = "BSD-2-Clause" repository = "https://github.com/vorot/roots" description = """ Library of well known algorithms for numerical root finding. """ readme = "README.md" keywords = [ "math", "root", "numerical", "cubic", "quartic" ] [badges] # Travis CI: `repository` is required. `branch` is optional; default is `master` travis-ci = { repository = "https://github.com/vorot/roots", branch = "master" } [lib] name = "roots" path = "src/lib.rs" [dev-dependencies] criterion = "0.3" [[bench]] name="benches" harness=false roots-0.0.8/LICENSE000064400000000000000000000024250072674642500120370ustar 00000000000000Copyright (c) 2015, Mikhail Vorotilov All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. roots-0.0.8/README.md000064400000000000000000000050630072674642500123120ustar 00000000000000Library of well known algorithms for numerical root finding. [![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause)[![Build Status](https://travis-ci.com/vorot/roots.svg)](https://travis-ci.com/vorot/roots)[![Crates.io](https://img.shields.io/crates/v/roots.svg)](https://crates.io/crates/roots) ## Features - Iterative approximation: - [Newton-Raphson](https://en.wikipedia.org/wiki/Newton%27s_method) method - [Secant](https://en.wikipedia.org/wiki/Secant_method) method - [Regula falsi](https://en.wikipedia.org/wiki/False_position_method) method (with Illinois modification) - [Brent-Dekker](https://en.wikipedia.org/wiki/Brent%27s_method) method - [Inverse quadratic](https://en.wikipedia.org/wiki/Inverse_quadratic_interpolation) approximation - Recursive [Sturm's](https://en.wikipedia.org/wiki/Sturm%27s_theorem) method - Solving polynomial equations - [Linear](https://en.wikipedia.org/wiki/Linear_equation) equation (editors' choice) - [Quadratic](https://en.wikipedia.org/wiki/Quadratic_equation) equation - [Cubic](https://en.wikipedia.org/wiki/Cubic_function) equation - [Quartic](https://en.wikipedia.org/wiki/Quartic_function) equation - [Eigenvalues](https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors) method for higher-degree polynomials ## Usage ```rust extern crate roots; use roots::Roots; use roots::find_roots_cubic; use roots::find_root_brent; use roots::find_root_secant; // Find the root of a complex function in the area determined by a simpler polynom fn find_solution(enormous_function: F, root_area_polynom:(f64,f64,f64,f64)) -> Option where F: Fn(f64) -> f64 { // de-structure polynom coefficients match root_area_polynom { (a3,a2,a1,a0) => { // Find root area by solving the polynom match find_roots_cubic(a3,a2,a1,a0) { // Try to find the root by one of iterative methods Roots::Three(roots) => { // Three roots found, normal case find_root_brent(roots[0],roots[2],enormous_function, &mut 1e-8f64).ok() }, Roots::Two(roots) => { // Two roots found, High precision required find_root_brent(roots[0],roots[1],enormous_function,&mut 1e-15f64).ok() }, Roots::One(roots) => { // One root found, Low precision is enough find_root_secant(roots[0]-1f64,roots[0]+1f64,enormous_function,&mut 1e-3f64).ok() }, _ => None, } }, _ => None, } } ``` roots-0.0.8/rustfmt.toml000064400000000000000000000000760072674642500134330ustar 00000000000000# Maximum width of each line. # Default: 100 max_width = 132roots-0.0.8/src/analytical/biquadratic.rs000064400000000000000000000066330072674642500166050ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a bi-quadratic equation a4*x^4 + a2*x^2 + a0 = 0. /// /// Returned roots are arranged in the increasing order. /// /// # Examples /// /// ``` /// use roots::find_roots_biquadratic; /// /// let no_roots = find_roots_biquadratic(1f32, 0f32, 1f32); /// // Returns Roots::No([]) as 'x^4 + 1 = 0' has no roots /// /// let one_root = find_roots_biquadratic(1f64, 0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^4 = 0' has one root 0 /// /// let two_roots = find_roots_biquadratic(1f32, 0f32, -1f32); /// // Returns Roots::Two([-1f32, 1f32]) as 'x^4 - 1 = 0' has roots -1 and 1 /// ``` pub fn find_roots_biquadratic(a4: F, a2: F, a0: F) -> Roots { // Handle non-standard cases if a4 == F::zero() { // a4 = 0; a2*x^2 + a0 = 0; solve quadratic equation super::quadratic::find_roots_quadratic(a2, F::zero(), a0) } else if a0 == F::zero() { // a0 = 0; a4*x^4 + a2*x^2 = 0; solve quadratic equation and add zero root super::quadratic::find_roots_quadratic(a4, F::zero(), a2).add_new_root(F::zero()) } else { // solve the corresponding quadratic equation and order roots let mut roots = Roots::No([]); for x in super::quadratic::find_roots_quadratic(a4, a2, a0).as_ref().iter() { if *x > F::zero() { let sqrt_x = x.sqrt(); roots = roots.add_new_root(-sqrt_x).add_new_root(sqrt_x); } else if *x == F::zero() { roots = roots.add_new_root(F::zero()); } } roots } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_biquadratic() { assert_eq!(find_roots_biquadratic(0f32, 0f32, 0f32), Roots::One([0f32])); assert_eq!(find_roots_biquadratic(1f32, 0f32, 1f32), Roots::No([])); assert_eq!(find_roots_biquadratic(1f64, 0f64, -1f64), Roots::Two([-1f64, 1f64])); assert_eq!( find_roots_biquadratic(1f64, -5f64, 4f64), Roots::Four([-2f64, -1f64, 1f64, 2f64]) ); } } roots-0.0.8/src/analytical/cubic.rs000064400000000000000000000160150072674642500153750ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a cubic equation a3*x^3 + a2*x^2 + a1*x + a0 = 0. /// /// General formula (complex numbers) is implemented for three roots. /// /// Note that very small values of a3 (comparing to other coefficients) will cause the loss of precision. /// /// In case more than one roots are present, they are returned in the increasing order. /// /// # Examples /// /// ``` /// use roots::Roots; /// use roots::find_roots_cubic; /// /// let no_roots = find_roots_cubic(0f32, 1f32, 0f32, 1f32); /// // Returns Roots::No([]) as 'x^2 + 1 = 0' has no roots /// /// let one_root = find_roots_cubic(1f64, 0f64, 0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^3 = 0' has one root 0 /// /// let three_roots = find_roots_cubic(1f32, 0f32, -1f32, 0f32); /// // Returns Roots::Three([-1f32, 0f32, 1f32]) as 'x^3 - x = 0' has roots -1, 0, and 1 /// /// let three_roots_less_precision = find_roots_cubic( /// -0.000000000000000040410628481035f64, /// 0.0126298310280606f64, /// -0.100896606408756f64, /// 0.0689539597036461f64); /// // Returns Roots::Three([0.7583841816097057f64, 7.233267996296344f64, 312537357195212.9f64]) /// // while online math expects 0.7547108770537f64, 7.23404258961f64, 312537357195213f64 /// ``` pub fn find_roots_cubic(a3: F, a2: F, a1: F, a0: F) -> Roots { // Handle non-standard cases if a3 == F::zero() { // a3 = 0; a2*x^2+a1*x+a0=0; solve quadratic equation super::quadratic::find_roots_quadratic(a2, a1, a0) } else if a2 == F::zero() { // a2 = 0; a3*x^3+a1*x+a0=0; solve depressed cubic equation super::cubic_depressed::find_roots_cubic_depressed(a1 / a3, a0 / a3) } else if a3 == F::one() { // solve normalized cubic expression super::cubic_normalized::find_roots_cubic_normalized(a2, a1, a0) } else { let _2 = F::from(2i16); let _3 = F::from(3i16); let _4 = F::from(4i16); let _9 = F::from(9i16); let _18 = F::from(18i16); let _27 = F::from(27i16); // standard case let d = _18 * a3 * a2 * a1 * a0 - _4 * a2 * a2 * a2 * a0 + a2 * a2 * a1 * a1 - _4 * a3 * a1 * a1 * a1 - _27 * a3 * a3 * a0 * a0; let d0 = a2 * a2 - _3 * a3 * a1; let d1 = _2 * a2 * a2 * a2 - _9 * a3 * a2 * a1 + _27 * a3 * a3 * a0; if d < F::zero() { // one real root let sqrt = (-_27 * a3 * a3 * d).sqrt(); let c = F::cbrt(if d1 < F::zero() { d1 - sqrt } else { d1 + sqrt } / _2); let x = -(a2 + c + d0 / c) / (_3 * a3); Roots::One([x]) } else if d == F::zero() { // multiple roots if d0 == F::zero() { // triple root Roots::One([-a2 / (a3 * _3)]) } else { // single root and double root Roots::One([(_9 * a3 * a0 - a2 * a1) / (d0 * _2)]) .add_new_root((_4 * a3 * a2 * a1 - _9 * a3 * a3 * a0 - a2 * a2 * a2) / (a3 * d0)) } } else { // three real roots let c3_img = F::sqrt(_27 * a3 * a3 * d) / _2; let c3_real = d1 / _2; let c3_module = F::sqrt(c3_img * c3_img + c3_real * c3_real); let c3_phase = _2 * F::atan(c3_img / (c3_real + c3_module)); let c_module = F::cbrt(c3_module); let c_phase = c3_phase / _3; let c_real = c_module * F::cos(c_phase); let c_img = c_module * F::sin(c_phase); let x0_real = -(a2 + c_real + (d0 * c_real) / (c_module * c_module)) / (_3 * a3); let e_real = -F::one() / _2; let e_img = F::sqrt(_3) / _2; let c1_real = c_real * e_real - c_img * e_img; let c1_img = c_real * e_img + c_img * e_real; let x1_real = -(a2 + c1_real + (d0 * c1_real) / (c1_real * c1_real + c1_img * c1_img)) / (_3 * a3); let c2_real = c1_real * e_real - c1_img * e_img; let c2_img = c1_real * e_img + c1_img * e_real; let x2_real = -(a2 + c2_real + (d0 * c2_real) / (c2_real * c2_real + c2_img * c2_img)) / (_3 * a3); Roots::One([x0_real]).add_new_root(x1_real).add_new_root(x2_real) } } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_cubic() { assert_eq!(find_roots_cubic(1f32, 0f32, 0f32, 0f32), Roots::One([0f32])); match find_roots_cubic(1f64, 0f64, -1f64, 0f64) { Roots::Three(x) => { assert_float_array_eq!(1e-15, x, [-1f64, 0f64, 1f64]); } _ => { assert!(false); } } } #[test] fn test_find_roots_cubic_small_discriminant() { // Try to find roots of the cubic polynomial where the highest coefficient is very small // (as reported by Andrew Hunter in July 2019) match find_roots_cubic( -0.000000000000000040410628481035f64, 0.0126298310280606f64, -0.100896606408756f64, 0.0689539597036461f64, ) { Roots::Three(x) => { // (According to Wolfram Alpha, roots must be 0.7547108770537f64, 7.23404258961f64, 312537357195213f64) // Actual result differ a little due to the limited precision of calculations. assert_float_array_eq!(1e-8, x, [0.7583841816097057f64, 7.233267996296344f64, 312537357195212.9f64]); } _ => { assert!(false); } } } } roots-0.0.8/src/analytical/cubic_depressed.rs000064400000000000000000000103630072674642500174330ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a depressed cubic equation x^3 + a1*x + a0 = 0. /// /// In case more than one roots are present, they are returned in the increasing order. /// /// # Examples /// /// ``` /// use roots::find_roots_cubic_depressed; /// /// let one_root = find_roots_cubic_depressed(0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^3 = 0' has one root 0 /// /// let three_roots = find_roots_cubic_depressed(-1f32, 0f32); /// // Returns Roots::Three([-1f32, -0f32, 1f32]) as 'x^3 - x = 0' has roots -1, 0, and 1 /// ``` pub fn find_roots_cubic_depressed(a1: F, a0: F) -> Roots { let _2 = F::from(2i16); let _3 = F::from(3i16); let _4 = F::from(4i16); let _9 = F::from(9i16); let _18 = F::from(18i16); let _27 = F::from(27i16); let _54 = F::from(54i16); if a1 == F::zero() { Roots::One([-a0.cbrt()]) } else if a0 == F::zero() { super::quadratic::find_roots_quadratic(F::one(), F::zero(), a1).add_new_root(F::zero()) } else { let d = a0 * a0 / _4 + a1 * a1 * a1 / _27; if d < F::zero() { // n*a0^2 + m*a1^3 < 0 => a1 < 0 let a = (-_4 * a1 / _3).sqrt(); let phi = (-_4 * a0 / (a * a * a)).acos() / _3; Roots::One([a * phi.cos()]) .add_new_root(a * (phi + F::two_third_pi()).cos()) .add_new_root(a * (phi - F::two_third_pi()).cos()) } else { let sqrt_d = d.sqrt(); let a0_div_2 = a0 / _2; let x1 = (sqrt_d - a0_div_2).cbrt() - (sqrt_d + a0_div_2).cbrt(); if d == F::zero() { // one real root and one double root Roots::One([x1]).add_new_root(a0_div_2) } else { // one real root Roots::One([x1]) } } } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_cubic_depressed() { assert_eq!(find_roots_cubic_depressed(0f32, 0f32), Roots::One([0f32])); assert_eq!(find_roots_cubic_depressed(-1f64, 0f64), Roots::Three([-1f64, 0f64, 1f64])); match find_roots_cubic_depressed(-2f64, 2f64) { Roots::One(x) => { assert_float_array_eq!(1e-15, x, [-1.769292354238631415240409f64]); } _ => { assert!(false); } } match find_roots_cubic_depressed(-3f64, 2f64) { Roots::Two(x) => { assert_float_array_eq!(1e-15, x, [-2f64, 1f64]); } _ => { assert!(false); } } match find_roots_cubic_depressed(-2f64, 1f64) { Roots::Three(x) => { assert_float_array_eq!(1e-15, x, [(-1f64 - 5f64.sqrt()) / 2f64, (-1f64 + 5f64.sqrt()) / 2f64, 1f64]); } _ => { assert!(false); } } } } roots-0.0.8/src/analytical/cubic_normalized.rs000064400000000000000000000137040072674642500176230ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a normalized cubic equation x^3 + a2*x^2 + a1*x + a0 = 0. /// /// Trigonometric solution (arccos/cos) is implemented for three roots. /// /// In case more than one roots are present, they are returned in the increasing order. /// /// # Examples /// /// ``` /// use roots::find_roots_cubic_normalized; /// /// let one_root = find_roots_cubic_normalized(0f64, 0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^3 = 0' has one root 0 /// /// let three_roots = find_roots_cubic_normalized(0f32, -1f32, 0f32); /// // Returns Roots::Three([-1f32, -0f32, 1f32]) as 'x^3 - x = 0' has roots -1, 0, and 1 /// ``` pub fn find_roots_cubic_normalized(a2: F, a1: F, a0: F) -> Roots { let _2 = F::from(2i16); let _3 = F::from(3i16); let _4 = F::from(4i16); let _9 = F::from(9i16); let _18 = F::from(18i16); let _27 = F::from(27i16); let _54 = F::from(54i16); let q = (_3 * a1 - a2 * a2) / _9; let r = (_9 * a2 * a1 - _27 * a0 - _2 * a2 * a2 * a2) / _54; let q3 = q * q * q; let d = q3 + r * r; let a2_div_3 = a2 / _3; if d < F::zero() { let phi_3 = (r / (-q3).sqrt()).acos() / _3; let sqrt_q_2 = _2 * (-q).sqrt(); Roots::One([sqrt_q_2 * phi_3.cos() - a2_div_3]) .add_new_root(sqrt_q_2 * (phi_3 - F::two_third_pi()).cos() - a2_div_3) .add_new_root(sqrt_q_2 * (phi_3 + F::two_third_pi()).cos() - a2_div_3) } else { let sqrt_d = d.sqrt(); let s = (r + sqrt_d).cbrt(); let t = (r - sqrt_d).cbrt(); if s == t { if s + t == F::zero() { Roots::One([s + t - a2_div_3]) } else { Roots::One([s + t - a2_div_3]).add_new_root(-(s + t) / _2 - a2_div_3) } } else { Roots::One([s + t - a2_div_3]) } } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_cubic_normalized() { assert_eq!(find_roots_cubic_normalized(0f32, 0f32, 0f32), Roots::One([0f32])); match find_roots_cubic_normalized(0f64, -1f64, 0f64) { Roots::Three(x) => { assert_float_array_eq!(1e-15, x, [-1f64, 0f64, 1f64]); } _ => { assert!(false); } } match find_roots_cubic_normalized(1f64, -2f64, 2f64) { Roots::One(x) => { assert_float_array_eq!(1e-15, x, [-2.269530842081142770853135f64]); } _ => { assert!(false); } } match find_roots_cubic_normalized(0f64, -3f64, 2f64) { Roots::Two(x) => { assert_float_array_eq!(1e-15, x, [-2f64, 1f64]); } _ => { assert!(false); } } match find_roots_cubic_normalized(-2f64, -3f64, 2f64) { Roots::Three(x) => { assert_float_array_eq!( 1e-15, x, [ -1.342923082777170208054859f64, 0.5293165801288393926136314f64, 2.813606502648330815441228f64 ] ); } _ => { assert!(false); } } } #[test] fn test_find_roots_cubic_normalized_huge_discriminant() { // Try to find roots of the cubic polynomial where the highest coefficient is very small // (as reported by Andrew Hunter in July 2019) match find_roots_cubic_normalized( 0.0126298310280606f64 / -0.000000000000000040410628481035f64, -0.100896606408756f64 / -0.000000000000000040410628481035f64, 0.0689539597036461f64 / -0.000000000000000040410628481035f64, ) { //Roots::Three(x) => { // // (According to Wolfram Alpha, roots must be 0.7547108770537f64, 7.23404258961f64, 312537357195213f64) // // These results are returned by find_roots_cubic (using complex numbers). // assert_float_array_eq!(1e-8, x, [0.7583841816097057f64, 7.233267996296344f64, 312537357195212.9f64]); //} Roots::One(x) => { // (According to Wolfram Alpha, roots must be 0.7547108770537f64, 7.23404258961f64, 312537357195213f64) // Due to the limited precision of calculations of arccos, the function cannot cope with such numbers. assert_float_array_eq!(1e-8, x, [312537357195212.4625f64]); } _ => { assert!(false); } } } } roots-0.0.8/src/analytical/linear.rs000064400000000000000000000051060072674642500155610ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a linear equation a1*x + a0 = 0. /// /// # Examples /// /// ``` /// use roots::Roots; /// use roots::find_roots_linear; /// /// // Returns Roots::No([]) as '0*x + 1 = 0' has no roots; /// let no_root = find_roots_linear(0f32, 1f32); /// assert_eq!(no_root, Roots::No([])); /// /// // Returns Roots::Two([0f64]) as '1*x + 0 = 0' has the root 0 /// let root = find_roots_linear(1f64, 0f64); /// assert_eq!(root, Roots::One([0f64])); /// /// // Returns Roots::One([0f32]) as 0 is one of roots of '0*x + 0 = 0' /// let zero_root = find_roots_linear(0f32, 0f32); /// assert_eq!(zero_root, Roots::One([0f32])); /// ``` pub fn find_roots_linear(a1: F, a0: F) -> Roots { if a1 == F::zero() { if a0 == F::zero() { Roots::One([F::zero()]) } else { Roots::No([]) } } else { Roots::One([-a0 / a1]) } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_linear() { assert_eq!(find_roots_linear(0f32, 0f32), Roots::One([0f32])); assert_eq!(find_roots_linear(2f64, 1f64), Roots::One([-0.5f64])); assert_eq!(find_roots_linear(0f32, 1f32), Roots::No([])); } } roots-0.0.8/src/analytical/mod.rs000064400000000000000000000030130072674642500150610ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pub mod biquadratic; pub mod cubic; pub mod cubic_depressed; pub mod cubic_normalized; pub mod linear; pub mod quadratic; pub mod quartic; pub mod quartic_depressed; pub mod roots; roots-0.0.8/src/analytical/quadratic.rs000064400000000000000000000121400072674642500162600ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a quadratic equation a2*x^2 + a1*x + a0 = 0. /// /// In case two roots are present, the first returned root is less than the second one. /// /// # Examples /// /// ``` /// use roots::Roots; /// use roots::find_roots_quadratic; /// /// let no_roots = find_roots_quadratic(1f32, 0f32, 1f32); /// // Returns Roots::No([]) as 'x^2 + 1 = 0' has no roots /// /// let one_root = find_roots_quadratic(1f64, 0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^2 = 0' has one root 0 /// /// let two_roots = find_roots_quadratic(1f32, 0f32, -1f32); /// // Returns Roots::Two([-1f32,1f32]) as 'x^2 - 1 = 0' has roots -1 and 1 /// ``` pub fn find_roots_quadratic(a2: F, a1: F, a0: F) -> Roots { // Handle non-standard cases if a2 == F::zero() { // a2 = 0; a1*x+a0=0; solve linear equation super::linear::find_roots_linear(a1, a0) } else { let _2 = F::from(2i16); let _4 = F::from(4i16); // Rust lacks a simple way to convert an integer constant to generic type F let discriminant = a1 * a1 - _4 * a2 * a0; if discriminant < F::zero() { Roots::No([]) } else { let a2x2 = _2 * a2; if discriminant == F::zero() { Roots::One([-a1 / a2x2]) } else { // To improve precision, do not use the smallest divisor. // See https://people.csail.mit.edu/bkph/articles/Quadratics.pdf let sq = discriminant.sqrt(); let (same_sign, diff_sign) = if a1 < F::zero() { (-a1 + sq, -a1 - sq) } else { (-a1 - sq, -a1 + sq) }; let (x1, x2) = if same_sign.abs() > a2x2.abs() { let a0x2 = _2 * a0; if diff_sign.abs() > a2x2.abs() { // 2*a2 is the smallest divisor, do not use it (a0x2 / same_sign, a0x2 / diff_sign) } else { // diff_sign is the smallest divisor, do not use it (a0x2 / same_sign, same_sign / a2x2) } } else { // 2*a2 is the greatest divisor, use it (diff_sign / a2x2, same_sign / a2x2) }; // Order roots if x1 < x2 { Roots::Two([x1, x2]) } else { Roots::Two([x2, x1]) } } } } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_quadratic() { assert_eq!(find_roots_quadratic(0f32, 0f32, 0f32), Roots::One([0f32])); assert_eq!(find_roots_quadratic(1f32, 0f32, 1f32), Roots::No([])); assert_eq!(find_roots_quadratic(1f64, 0f64, -1f64), Roots::Two([-1f64, 1f64])); } #[test] fn test_find_roots_quadratic_small_a2() { assert_eq!( find_roots_quadratic(1e-20f32, -1f32, -1e-30f32), Roots::Two([-1e-30f32, 1e20f32]) ); assert_eq!( find_roots_quadratic(-1e-20f32, 1f32, 1e-30f32), Roots::Two([-1e-30f32, 1e20f32]) ); assert_eq!(find_roots_quadratic(1e-20f32, -1f32, 1f32), Roots::Two([1f32, 1e20f32])); assert_eq!(find_roots_quadratic(-1e-20f32, 1f32, 1f32), Roots::Two([-1f32, 1e20f32])); assert_eq!(find_roots_quadratic(-1e-20f32, 1f32, -1f32), Roots::Two([1f32, 1e20f32])); } #[test] fn test_find_roots_quadratic_big_a1() { assert_eq!(find_roots_quadratic(1f32, -1e15f32, -1f32), Roots::Two([-1e-15f32, 1e15f32])); assert_eq!(find_roots_quadratic(-1f32, 1e15f32, 1f32), Roots::Two([-1e-15f32, 1e15f32])); } } roots-0.0.8/src/analytical/quartic.rs000064400000000000000000000272150072674642500157640ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a quartic equation a4*x^4 + a4*x^3 + a2*x^2 + a1*x + a0 = 0. /// pp, rr, and dd are already computed while searching for multiple roots fn find_roots_via_depressed_quartic(a4: F, a3: F, a2: F, a1: F, a0: F, pp: F, rr: F, dd: F) -> Roots { // Depressed quartic // https://en.wikipedia.org/wiki/Quartic_function#Converting_to_a_depressed_quartic let _2 = F::from(2i16); let _3 = F::from(3i16); let _4 = F::from(4i16); let _6 = F::from(6i16); let _8 = F::from(8i16); let _12 = F::from(12i16); let _16 = F::from(16i16); let _256 = F::from(256i16); // a4*x^4 + a3*x^3 + a2*x^2 + a1*x + a0 = 0 => y^4 + p*y^2 + q*y + r. let a4_pow_2 = a4 * a4; let a4_pow_3 = a4_pow_2 * a4; let a4_pow_4 = a4_pow_2 * a4_pow_2; // Re-use pre-calculated values let p = pp / (_8 * a4_pow_2); let q = rr / (_8 * a4_pow_3); let r = (dd + _16 * a4_pow_2 * (_12 * a0 * a4 - _3 * a1 * a3 + a2 * a2)) / (_256 * a4_pow_4); let mut roots = Roots::No([]); for y in super::quartic_depressed::find_roots_quartic_depressed(p, q, r) .as_ref() .iter() { roots = roots.add_new_root(*y - a3 / (_4 * a4)); } roots } /// Solves a quartic equation a4*x^4 + a3*x^3 + a2*x^2 + a1*x + a0 = 0. /// /// Returned roots are ordered. /// Precision is about 5e-15 for f64, 5e-7 for f32. /// WARNING: f32 is often not enough to find multiple roots. /// /// # Examples /// /// ``` /// use roots::find_roots_quartic; /// /// let one_root = find_roots_quartic(1f64, 0f64, 0f64, 0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^4 = 0' has one root 0 /// /// let two_roots = find_roots_quartic(1f32, 0f32, 0f32, 0f32, -1f32); /// // Returns Roots::Two([-1f32, 1f32]) as 'x^4 - 1 = 0' has roots -1 and 1 /// /// let multiple_roots = find_roots_quartic(-14.0625f64, -3.75f64, 29.75f64, 4.0f64, -16.0f64); /// // Returns Roots::Two([-1.1016116464173349f64, 0.9682783130840016f64]) /// /// let multiple_roots_not_found = find_roots_quartic(-14.0625f32, -3.75f32, 29.75f32, 4.0f32, -16.0f32); /// // Returns Roots::No([]) because of f32 rounding errors when trying to calculate the discriminant /// ``` pub fn find_roots_quartic(a4: F, a3: F, a2: F, a1: F, a0: F) -> Roots { // Handle non-standard cases if a4 == F::zero() { // a4 = 0; a3*x^3 + a2*x^2 + a1*x + a0 = 0; solve cubic equation super::cubic::find_roots_cubic(a3, a2, a1, a0) } else if a0 == F::zero() { // a0 = 0; x^4 + a2*x^2 + a1*x = 0; reduce to cubic and arrange results super::cubic::find_roots_cubic(a4, a3, a2, a1).add_new_root(F::zero()) } else if a1 == F::zero() && a3 == F::zero() { // a1 = 0, a3 =0; a4*x^4 + a2*x^2 + a0 = 0; solve bi-quadratic equation super::biquadratic::find_roots_biquadratic(a4, a2, a0) } else { let _3 = F::from(3i16); let _4 = F::from(4i16); let _6 = F::from(6i16); let _8 = F::from(8i16); let _9 = F::from(9i16); let _10 = F::from(10i16); let _12 = F::from(12i16); let _16 = F::from(16i16); let _18 = F::from(18i16); let _27 = F::from(27i16); let _64 = F::from(64i16); let _72 = F::from(72i16); let _80 = F::from(80i16); let _128 = F::from(128i16); let _144 = F::from(144i16); let _192 = F::from(192i16); let _256 = F::from(256i16); // Discriminant // https://en.wikipedia.org/wiki/Quartic_function#Nature_of_the_roots // Partially simplifed to keep intermediate values smaller (to minimize rounding errors). let discriminant = a4 * a0 * a4 * (_256 * a4 * a0 * a0 + a1 * (_144 * a2 * a1 - _192 * a3 * a0)) + a4 * a0 * a2 * a2 * (_16 * a2 * a2 - _80 * a3 * a1 - _128 * a4 * a0) + (a3 * a3 * (a4 * a0 * (_144 * a2 * a0 - _6 * a1 * a1) + (a0 * (_18 * a3 * a2 * a1 - _27 * a3 * a3 * a0 - _4 * a2 * a2 * a2) + a1 * a1 * (a2 * a2 - _4 * a3 * a1)))) + a4 * a1 * a1 * (_18 * a3 * a2 * a1 - _27 * a4 * a1 * a1 - _4 * a2 * a2 * a2); let pp = _8 * a4 * a2 - _3 * a3 * a3; let rr = a3 * a3 * a3 + _8 * a4 * a4 * a1 - _4 * a4 * a3 * a2; let delta0 = a2 * a2 - _3 * a3 * a1 + _12 * a4 * a0; let dd = _64 * a4 * a4 * a4 * a0 - _16 * a4 * a4 * a2 * a2 + _16 * a4 * a3 * a3 * a2 - _16 * a4 * a4 * a3 * a1 - _3 * a3 * a3 * a3 * a3; // Handle special cases let double_root = discriminant == F::zero(); if double_root { let triple_root = double_root && delta0 == F::zero(); let quadruple_root = triple_root && dd == F::zero(); let no_roots = dd == F::zero() && pp > F::zero() && rr == F::zero(); if quadruple_root { // Wiki: all four roots are equal Roots::One([-a3 / (_4 * a4)]) } else if triple_root { // Wiki: At least three roots are equal to each other // x0 is the unique root of the remainder of the Euclidean division of the quartic by its second derivative // // Solved by SymPy (ra is the desired reminder) // a, b, c, d, e = symbols('a,b,c,d,e') // f=a*x**4+b*x**3+c*x**2+d*x+e // Quartic polynom // g=6*a*x**2+3*b*x+c // Second derivative // q, r = div(f, g) // SymPy only finds the highest power // simplify(f-(q*g+r)) == 0 // Verify the first division // qa, ra = div(r/a,g/a) // Workaround to get the second division // simplify(f-((q+qa)*g+ra*a)) == 0 // Verify the second division // solve(ra,x) // ----- yields // (āˆ’72*a^2*e+10*a*c^2āˆ’3*b^2*c)/(9*(8*a^2*dāˆ’4*a*b*c+b^3)) let x0 = (-_72 * a4 * a4 * a0 + _10 * a4 * a2 * a2 - _3 * a3 * a3 * a2) / (_9 * (_8 * a4 * a4 * a1 - _4 * a4 * a3 * a2 + a3 * a3 * a3)); let roots = Roots::One([x0]); roots.add_new_root(-(a3 / a4 + _3 * x0)) } else if no_roots { // Wiki: two complex conjugate double roots Roots::No([]) } else { find_roots_via_depressed_quartic(a4, a3, a2, a1, a0, pp, rr, dd) } } else { let no_roots = discriminant > F::zero() && (pp > F::zero() || dd > F::zero()); if no_roots { // Wiki: two pairs of non-real complex conjugate roots Roots::No([]) } else { find_roots_via_depressed_quartic(a4, a3, a2, a1, a0, pp, rr, dd) } } } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_quartic() { assert_eq!(find_roots_quartic(1f32, 0f32, 0f32, 0f32, 0f32), Roots::One([0f32])); assert_eq!(find_roots_quartic(1f64, 0f64, 0f64, 0f64, -1f64), Roots::Two([-1f64, 1f64])); assert_eq!( find_roots_quartic(1f64, -10f64, 35f64, -50f64, 24f64), Roots::Four([1f64, 2f64, 3f64, 4f64]) ); match find_roots_quartic(1.1248467624839498f64, -4.8721513473605924f64, 7.9323705711747614f64, -5.7774307699949397f64, 1.5971379368787519f64) { Roots::Two(x) => { assert_float_array_eq!(2e-15f64, x, [1.225913506454221f64, 1.257275575390252f64]); } _ => { assert!(false); } } match find_roots_quartic(3f64, 5f64, -5f64, -5f64, 2f64) { Roots::Four(x) => { assert_float_array_eq!(2e-15f64, x, [-2f64, -1f64, 0.33333333333333333f64, 1f64]); } _ => { assert!(false); } } match find_roots_quartic(3f32, 5f32, -5f32, -5f32, 2f32) { Roots::Four(x) => { assert_float_array_eq!(5e-7, x, [-2f32, -1f32, 0.33333333333333333f32, 1f32]); } _ => { assert!(false); } } } #[test] fn test_find_roots_quartic_tim_luecke() { // Reported in December 2019 assert_eq!( find_roots_quartic(-14.0625f64, -3.75f64, 29.75f64, 4.0f64, -16.0f64), Roots::Two([-1.1016116464173349f64, 0.9682783130840016f64]) ); // 32-bit floating point is not accurate enough to solve this case ... assert_eq!( find_roots_quartic(-14.0625f32, -3.75f32, 29.75f32, 4.0f32, -16.0f32), Roots::No([]) ); // ... even after normalizing assert_eq!( find_roots_quartic( 1f32, -3.75f32 / -14.0625f32, 29.75f32 / -14.0625f32, 4.0f32 / -14.0625f32, -16.0f32 / -14.0625f32 ), Roots::No([]) ); // assert_eq!( // find_roots_quartic(-14.0625f32, -3.75f32, 29.75f32, 4.0f32, -16.0f32), // Roots::Two([-1.1016117f32, 0.96827835f32]) // ); } #[test] fn test_find_roots_quartic_triple_root() { // (x+3)(3x-1)^3 == 27 x^4 + 54 x^3 - 72 x^2 + 26 x - 3 assert_eq!( find_roots_quartic(27f64, 54f64, -72f64, 26f64, -3f64), Roots::Two([-3.0f64, 0.3333333333333333f64]) ); assert_eq!( find_roots_quartic(27f32, 54f32, -72f32, 26f32, -3f32), Roots::Two([-3.0f32, 0.33333333f32]) ); } #[test] fn test_find_roots_quartic_quadruple_root() { // (7x+2)^4 == 2401 x^4 + 2744 x^3 + 1176 x^2 + 224 x + 16 assert_eq!( find_roots_quartic(2401f64, 2744f64, 1176f64, 224f64, 16f64), Roots::One([-0.2857142857142857f64]) ); // 32-bit floating point is less accurate assert_eq!( find_roots_quartic(2401f32, 2744f32, 1176f32, 224f32, 16f32), Roots::One([-0.2857143f32]) ); } } roots-0.0.8/src/analytical/quartic_depressed.rs000064400000000000000000000115710072674642500200200ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::super::Roots; /// Solves a depressed quartic equation x^4 + a2*x^2 + a1*x + a0 = 0. /// /// Returned roots are ordered. Precision is about 1e-14 for f64. /// /// # Examples /// /// ``` /// use roots::find_roots_quartic_depressed; /// /// let one_root = find_roots_quartic_depressed(1f64, 0f64, 0f64); /// // Returns Roots::One([0f64]) as 'x^4 = 0' has one root 0 /// /// let two_roots = find_roots_quartic_depressed(1f32, 0f32, -1f32); /// // Returns Roots::Two([-1f32, 1f32]) as 'x^4 - 1 = 0' has roots -1 and 1 /// ``` pub fn find_roots_quartic_depressed(a2: F, a1: F, a0: F) -> Roots { // Handle non-standard cases if a1 == F::zero() { // a1 = 0; x^4 + a2*x^2 + a0 = 0; solve biquadratic equation super::biquadratic::find_roots_biquadratic(F::one(), a2, a0) } else if a0 == F::zero() { // a0 = 0; x^4 + a2*x^2 + a1*x = 0; reduce to normalized cubic and add zero root super::cubic_normalized::find_roots_cubic_normalized(F::zero(), a2, a1).add_new_root(F::zero()) } else { let _2 = F::from(2i16); let _5 = F::from(5i16); // Solve the auxiliary equation y^3 + (5/2)*a2*y^2 + (2*a2^2-a0)*y + (a2^3/2 - a2*a0/2 - a1^2/8) = 0 let a2_pow_2 = a2 * a2; let a1_div_2 = a1 / _2; let b2 = a2 * _5 / _2; let b1 = _2 * a2_pow_2 - a0; let b0 = (a2_pow_2 * a2 - a2 * a0 - a1_div_2 * a1_div_2) / _2; // At least one root always exists. The last root is the maximal one. let resolvent_roots = super::cubic_normalized::find_roots_cubic_normalized(b2, b1, b0); let y = resolvent_roots.as_ref().iter().last().unwrap(); let _a2_plus_2y = a2 + _2 * *y; if _a2_plus_2y > F::zero() { let sqrt_a2_plus_2y = _a2_plus_2y.sqrt(); let q0a = a2 + *y - a1_div_2 / sqrt_a2_plus_2y; let q0b = a2 + *y + a1_div_2 / sqrt_a2_plus_2y; let mut roots = super::quadratic::find_roots_quadratic(F::one(), sqrt_a2_plus_2y, q0a); for x in super::quadratic::find_roots_quadratic(F::one(), -sqrt_a2_plus_2y, q0b) .as_ref() .iter() { roots = roots.add_new_root(*x); } roots } else { Roots::No([]) } } } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_quartic_depressed() { assert_eq!(find_roots_quartic_depressed(0f32, 0f32, 0f32), Roots::One([0f32])); assert_eq!(find_roots_quartic_depressed(1f32, 1f32, 1f32), Roots::No([])); // Thanks WolframAlpha for the test data match find_roots_quartic_depressed(1f64, 1f64, -1f64) { Roots::Two(x) => { assert_float_array_eq!(1e-15, x, [-1f64, 0.5698402909980532659114f64]); } _ => { assert!(false); } } match find_roots_quartic_depressed(-10f64, 5f64, 1f64) { Roots::Four(x) => { assert_float_array_eq!( 1e-15, x, [ -3.3754294311910698f64, -0.1531811728532153f64, 0.67861075799846644f64, 2.84999984604581877f64 ] ); } _ => { assert!(false); } } } } roots-0.0.8/src/analytical/roots.rs000064400000000000000000000106360072674642500154610ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; /// Sorted and unique list of roots of an equation. #[derive(Debug, PartialEq)] pub enum Roots { /// Equation has no roots No([F; 0]), /// Equation has one root (or all roots of the equation are the same) One([F; 1]), /// Equation has two roots Two([F; 2]), /// Equation has three roots Three([F; 3]), /// Equation has four roots Four([F; 4]), } impl AsRef<[F]> for Roots { fn as_ref(&self) -> &[F] { match self { &Roots::No(ref x) => x, &Roots::One(ref x) => x, &Roots::Two(ref x) => x, &Roots::Three(ref x) => x, &Roots::Four(ref x) => x, } } } impl Roots { fn check_new_root(&self, new_root: F) -> (bool, usize) { let mut pos = 0; let mut exists = false; for x in self.as_ref().iter() { if *x == new_root { exists = true; break; } if *x > new_root { break; } pos = pos + 1; } (exists, pos) } /// Add a new root to existing ones keeping the list of roots ordered and unique. pub fn add_new_root(self, new_root: F) -> Self { match self { Roots::No(_) => Roots::One([new_root]), _ => { let (exists, pos) = self.check_new_root(new_root); if exists { self } else { let old_roots = self.as_ref(); match (old_roots.len(), pos) { (1, 0) => Roots::Two([new_root, old_roots[0]]), (1, 1) => Roots::Two([old_roots[0], new_root]), (2, 0) => Roots::Three([new_root, old_roots[0], old_roots[1]]), (2, 1) => Roots::Three([old_roots[0], new_root, old_roots[1]]), (2, 2) => Roots::Three([old_roots[0], old_roots[1], new_root]), (3, 0) => Roots::Four([new_root, old_roots[0], old_roots[1], old_roots[2]]), (3, 1) => Roots::Four([old_roots[0], new_root, old_roots[1], old_roots[2]]), (3, 2) => Roots::Four([old_roots[0], old_roots[1], new_root, old_roots[2]]), (3, 3) => Roots::Four([old_roots[0], old_roots[1], old_roots[2], new_root]), _ => panic!("Cannot add root"), } } } } } } #[test] fn test_roots() { let mut roots = Roots::One([1f32]); assert_eq!(roots, Roots::One([1f32])); roots = roots.add_new_root(1f32); assert_eq!(roots, Roots::One([1f32])); roots = roots.add_new_root(0f32); assert_eq!(roots, Roots::Two([0f32, 1f32])); roots = roots.add_new_root(0f32); assert_eq!(roots, Roots::Two([0f32, 1f32])); roots = roots.add_new_root(3f32); assert_eq!(roots, Roots::Three([0f32, 1f32, 3f32])); roots = roots.add_new_root(2f32); assert_eq!(roots, Roots::Four([0f32, 1f32, 2f32, 3f32])); } roots-0.0.8/src/float.rs000064400000000000000000000077720072674642500133060ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::f32; use std::f64; use std::fmt::Debug; use std::ops::Add; use std::ops::Div; use std::ops::Mul; use std::ops::Neg; use std::ops::Sub; /// Generic type that lists functions and constants needed in calculations. /// Default implementations for f32 and f64 are provided. pub trait FloatType: Sized + Copy + Debug + From + PartialEq + PartialOrd + Neg + Add + Sub + Mul + Div { fn zero() -> Self; fn one() -> Self; fn one_third() -> Self; fn pi() -> Self; fn two_third_pi() -> Self; fn sqrt(self) -> Self; /// The cubic root function is pow(x, 1/3) accepting negative arguments fn cbrt(self) -> Self { if self < Self::zero() { -(-self).powf(Self::one_third()) } else { self.powf(Self::one_third()) } } fn atan(self) -> Self; fn acos(self) -> Self; fn sin(self) -> Self; fn cos(self) -> Self; fn abs(self) -> Self; fn powf(self, n: Self) -> Self; } impl FloatType for f32 { #[inline] fn zero() -> Self { 0f32 } #[inline] fn one_third() -> Self { 1f32 / 3f32 } #[inline] fn one() -> Self { 1f32 } #[inline] fn two_third_pi() -> Self { 2f32 * f32::consts::FRAC_PI_3 } #[inline] fn pi() -> Self { f32::consts::PI } fn sqrt(self) -> Self { self.sqrt() } fn atan(self) -> Self { self.atan() } fn acos(self) -> Self { self.acos() } fn sin(self) -> Self { self.sin() } fn cos(self) -> Self { self.cos() } fn abs(self) -> Self { self.abs() } fn powf(self, n: Self) -> Self { self.powf(n) } } impl FloatType for f64 { #[inline] fn zero() -> Self { 0f64 } #[inline] fn one_third() -> Self { 1f64 / 3f64 } #[inline] fn one() -> Self { 1f64 } #[inline] fn two_third_pi() -> Self { 2f64 * f64::consts::FRAC_PI_3 } #[inline] fn pi() -> Self { f64::consts::PI } fn sqrt(self) -> Self { self.sqrt() } fn atan(self) -> Self { self.atan() } fn acos(self) -> Self { self.acos() } fn sin(self) -> Self { self.sin() } fn cos(self) -> Self { self.cos() } fn abs(self) -> Self { self.abs() } fn powf(self, n: Self) -> Self { self.powf(n) } } #[test] fn test_float_cbrt() { assert_eq!(-8f64.cbrt(), -2f64); assert_eq!(8f64.cbrt(), 2f64); assert_eq!(0f32.cbrt(), 0f32); } roots-0.0.8/src/lib.rs000064400000000000000000000105240072674642500127340ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#![crate_id = "roots"] #![crate_type = "lib"] //! A set of functions to find real roots of numerical equations. //! //! This crate contains various algorithms for numerical and analytical solving //! of 1-variable equations like f(x)=0. Only real roots are calculated. //! Multiple (double etc.) roots are considered as one root. //! //! # Use //! //! Functions find_root_* try to find a root of any given closure function by //! iterative approximations. Conditions for success/failure can be customized //! by implementing the Convergency trait. //! Functions find_roots_* return all roots of several simple equations at once. #[cfg(test)] macro_rules! assert_float_eq( ($precision:expr, $given:expr , $expected:expr) => ({ match (&($precision), &($given), &($expected)) { (precision_val, given_val, expected_val) => { let diff = given_val-expected_val; if diff.abs() > precision_val.abs() { panic!("floats are not the same: (`{}`: `{:.15e}`, expected: `{:.15e}`, precision: `{:.15e}`, delta: `{:.15e}`)", stringify!($given), *given_val, *expected_val, *precision_val, diff ) } } } }) ); #[cfg(test)] macro_rules! assert_float_array_eq( ($precision:expr, $given:expr , $expected:expr) => ({ match (&($precision), &($given), &($expected)) { (precision_val, given_val, expected_val) => { assert_eq!(given_val.len(),expected_val.len()); for i in 0..given_val.len() { assert_float_eq!(precision_val,given_val[i],expected_val[i]); } } } }) ); mod analytical; mod float; mod numerical; pub use self::float::FloatType; pub use self::analytical::biquadratic::find_roots_biquadratic; pub use self::analytical::cubic::find_roots_cubic; pub use self::analytical::cubic_depressed::find_roots_cubic_depressed; pub use self::analytical::cubic_normalized::find_roots_cubic_normalized; pub use self::analytical::linear::find_roots_linear; pub use self::analytical::quadratic::find_roots_quadratic; pub use self::analytical::quartic::find_roots_quartic; pub use self::analytical::quartic_depressed::find_roots_quartic_depressed; pub use self::analytical::roots::Roots; pub use self::numerical::brent::find_root_brent; pub use self::numerical::debug_convergency::DebugConvergency; pub use self::numerical::eigen::find_roots_eigen; pub use self::numerical::inverse_quadratic::find_root_inverse_quadratic; pub use self::numerical::inverse_quadratic::Parabola; pub use self::numerical::newton_raphson::find_root_newton_raphson; pub use self::numerical::polynom::find_roots_sturm; pub use self::numerical::regula_falsi::find_root_regula_falsi; pub use self::numerical::secant::find_root_secant; pub use self::numerical::simple_convergency::SimpleConvergency; pub use self::numerical::Convergency; pub use self::numerical::Interval; pub use self::numerical::Sample; pub use self::numerical::SearchError; roots-0.0.8/src/numerical/brent.rs000064400000000000000000000123200072674642500152530ustar 00000000000000// Copyright 2015 Mikhail Vorotilov. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use super::super::FloatType; use super::Convergency; use super::SearchError; /// Arrange two points so that the greatest value is first fn arrange(a: F, ya: F, b: F, yb: F) -> (F, F, F, F) { if ya.abs() > yb.abs() { (a, ya, b, yb) } else { (b, yb, a, ya) } } /// Find a root of the function f(x) = 0 using the Brent method. /// /// Pro /// /// + Fast /// + Robust /// + No need for derivative function /// /// Contra /// /// - Complicated /// - Needs initial bracketing /// /// # Failures /// ## NoBracketing /// Initial values do not bracket the root. /// ## NoConvergency /// Algorithm cannot find a root within the given number of iterations. /// # Examples /// /// ``` /// use roots::SimpleConvergency; /// use roots::find_root_brent; /// /// let f = |x| { 1f64*x*x - 1f64 }; /// let mut convergency = SimpleConvergency { eps:1e-15f64, max_iter:30 }; /// /// let root1 = find_root_brent(10f64, 0f64, &f, &mut convergency); /// // Returns approximately Ok(1); /// /// let root2 = find_root_brent(-10f64, 0f64, &f, &mut 1e-15f64); /// // Returns approximately Ok(-1); /// ``` pub fn find_root_brent(a: F, b: F, mut f: Func, convergency: &mut dyn Convergency) -> Result where F: FloatType, Func: FnMut(F) -> F, { let (mut a, mut ya, mut b, mut yb) = arrange(a, f(a), b, f(b)); if ya * yb > F::zero() { return Err(SearchError::NoBracketing); } let (mut c, mut yc, mut d) = (a, ya, a); let mut flag = true; let _2 = F::from(2i16); let _3 = F::from(3i16); let _4 = F::from(4i16); let mut iter = 0; loop { if convergency.is_root_found(ya) { return Ok(a); } if convergency.is_root_found(yb) { return Ok(b); } if convergency.is_converged(a, b) { return Ok(c); } let mut s = if (ya != yc) && (yb != yc) { a * yb * yc / ((ya - yb) * (ya - yc)) + b * ya * yc / ((yb - ya) * (yb - yc)) + c * ya * yb / ((yc - ya) * (yc - yb)) } else { b - yb * (b - a) / (yb - ya) }; let cond1 = (s - b) * (s - (_3 * a + b) / _4) > F::zero(); let cond2 = flag && (s - b).abs() >= (b - c).abs() / _2; let cond3 = !flag && (s - b).abs() >= (c - d).abs() / _2; let cond4 = flag && convergency.is_converged(b, c); let cond5 = !flag && convergency.is_converged(c, d); if cond1 || cond2 || cond3 || cond4 || cond5 { s = (a + b) / _2; flag = true; } else { flag = false; } let ys = f(s); d = c; c = b; yc = yb; if ya * ys < F::zero() { // Root bracketed between a ans s match arrange(a, f(a), s, ys) { (_a, _ya, _b, _yb) => { a = _a; ya = _ya; b = _b; yb = _yb; } } } else { // Root bracketed between s ans b match arrange(s, ys, b, f(b)) { (_a, _ya, _b, _yb) => { a = _a; ya = _ya; b = _b; yb = _yb; } } } iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { return Err(SearchError::NoConvergency); } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn test_find_root_brent() { let f = |x| 1f64 * x * x - 1f64; let mut conv = debug_convergency::DebugConvergency::new(1e-15f64, 30); conv.reset(); assert_float_eq!(1e-15f64, find_root_brent(10f64, 0f64, &f, &mut conv).ok().unwrap(), 1f64); assert_eq!(10, conv.get_iter_count()); conv.reset(); assert_float_eq!(1e-15f64, find_root_brent(-10f64, 0f64, &f, &mut conv).ok().unwrap(), -1f64); assert_eq!(10, conv.get_iter_count()); conv.reset(); assert_eq!(find_root_brent(10f64, 20f64, &f, &mut conv), Err(SearchError::NoBracketing)); assert_eq!(0, conv.get_iter_count()); } #[test] fn test_find_root_brent_simple() { let f = |x| 1f64 * x * x - 1f64; assert_float_eq!(1e-15f64, find_root_brent(10f64, 0f64, &f, &mut 1e-15f64).ok().unwrap(), 1f64); assert_float_eq!( 1e-15f64, find_root_brent(-10f64, 0f64, &f, &mut 1e-15f64).ok().unwrap(), -1f64 ); assert_eq!( find_root_brent(10f64, 20f64, &f, &mut 1e-15f64), Err(SearchError::NoBracketing) ); } } roots-0.0.8/src/numerical/debug_convergency.rs000064400000000000000000000054740072674642500176450ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::Convergency; use std::fmt::Display; use std::fmt::LowerExp; /// Convergency provider for debugging. /// It will print out the error at each iteration. pub struct DebugConvergency { /// Precision for both X and Y axes eps: F, /// Maximum number of iterations max_iter: usize, /// Last iteration iter: usize, } impl DebugConvergency { pub fn new(eps: F, max_iter: usize) -> DebugConvergency { DebugConvergency { eps: eps, max_iter: max_iter, iter: 0, } } pub fn reset(self: &mut DebugConvergency) { self.iter = 0; } pub fn get_iter_count(self: &DebugConvergency) -> usize { self.iter } } impl Convergency for DebugConvergency { /// Prints the value being checked fn is_root_found(&mut self, y: F) -> bool { println!("#{} check root {:.15e}", self.iter, y); y.abs() < self.eps.abs() } /// Prints values being checked fn is_converged(&mut self, x1: F, x2: F) -> bool { println!("#{} check convergency {:.15e}-{:.15e}", self.iter, x1, x2); (x1 - x2).abs() < self.eps.abs() } /// Updates internal iteration counter fn is_iteration_limit_reached(&mut self, iter: usize) -> bool { println!("#{} check iteration limit {}", self.iter, iter); self.iter = iter; iter >= self.max_iter } } roots-0.0.8/src/numerical/eigen.rs000064400000000000000000000656020072674642500152430ustar 00000000000000/* eigen.rs 0.2 This piece of code is transpiled EigenvalueDecomposition.java from Jama framework. I had to do this, because I haven't found any buildable rust opensource project to calculate real eigen values in Rust. There are many packages which are usually bound to openBLAS, but I can't build them with gnu toolchain, and that's the only toolchain, which allows gdb (and thus IDE) debug nowadays. Quality code is far from perfect, hopefully someone will appreciate my one day of manual code conversion nightmare and mention me in the source code. Stepan Yakovenko, https://github.com/stiv-yakovenko */ /* Added to roots 0.0.5 by Mikhail Vorotilov on request of Stepan Yakovenko */ use std::cmp; use std::collections::VecDeque; use std::fmt; use std::ops::Index; use std::ops::IndexMut; use super::FloatType; pub struct Matrix { data: VecDeque, n: usize, } impl Matrix { pub fn new(n: usize) -> Matrix { let mut data = VecDeque::new(); data.resize(n * n, 0.); Matrix { data: data, n: n } } } impl fmt::Debug for Matrix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{{").ok(); for r in 0..self.n { for c in 0..self.n { write!(f, "{:.3?} ", self[[c, r]]).ok(); } writeln!(f, "").ok(); } write!(f, "}}") } } impl Index<[usize; 2]> for Matrix { type Output = f64; fn index(&self, idx: [usize; 2]) -> &f64 { &self.data[idx[0] + self.n * idx[1]] } } impl IndexMut<[usize; 2]> for Matrix { fn index_mut(&mut self, idx: [usize; 2]) -> &mut f64 { self.data.get_mut(idx[0] + self.n * idx[1]).unwrap() } } fn cdiv(xr: f64, xi: f64, yr: f64, yi: f64) -> (f64, f64) { let r: f64; let d: f64; if yr.abs() > yi.abs() { r = yi / yr; d = yr + r * yi; ((xr + r * xi) / d, (xi - r * xr) / d) } else { r = yr / yi; d = yi + r * yr; ((r * xr + xi) / d, (r * xi - xr) / d) } } pub fn hqr2(n_in: usize, h: &mut Matrix, v: &mut Matrix, d: &mut Vec, e: &mut Vec) { // This is derived from the Algol procedure hqr2, // by Martin and Wilkinson, Handbook for Auto. Comp., // Vol.ii-Linear Algebra, and the corresponding // Fortran subroutine in EISPACK. // Initialize let nn = n_in; let mut n = nn as i16 - 1; let low = 0; let high = nn - 1; let eps = (2.0).powf(-52.0); let mut exshift = 0.0; let mut p = 0.; let mut q = 0.; let mut r = 0.; let mut s = 0.; let mut z = 0.; let mut t; let mut w; let mut x; let mut y; // Store roots isolated by balanc and compute matrix norm let mut norm = 0.0; let mut i = 0 as usize; while i < nn { if i < low || i > high { d[i] = h[[i, i]]; e[i] = 0.0; } let mut j = cmp::max(i as i16 - 1, 0) as usize; while j < nn { norm = norm + (h[[i, j]]).abs(); j = j + 1; } i = i + 1; } // Outer loop over eigenvalue index let mut iter = 0; while n >= low as i16 { // Look for single small sub-diagonal element let mut l = n; while l > low as i16 { s = (h[[l as usize - 1, l as usize - 1]]).abs() + (h[[l as usize, l as usize]]).abs(); if s == 0.0 { s = norm; } if (h[[l as usize, l as usize - 1]]).abs() < eps * s { break; } l = l - 1; } // Check for convergence // One root found if l == n { h[[n as usize, n as usize]] = h[[n as usize, n as usize]] + exshift; d[n as usize] = h[[n as usize, n as usize]]; e[n as usize] = 0.0; n = n - 1; iter = 0; // Two roots found } else if l == n - 1 { w = h[[n as usize, n as usize - 1]] * h[[n as usize - 1, n as usize]]; p = (h[[n as usize - 1, n as usize - 1]] - h[[n as usize, n as usize]]) / 2.0; q = p * p + w; z = (q).abs().sqrt(); h[[n as usize, n as usize]] = h[[n as usize, n as usize]] + exshift; h[[n as usize - 1, n as usize - 1]] = h[[n as usize - 1, n as usize - 1]] + exshift; x = h[[n as usize, n as usize]]; // Real pair if q >= 0. { if p >= 0. { z = p + z; } else { z = p - z; } d[n as usize - 1] = x + z; d[n as usize] = d[n as usize - 1]; if z != 0.0 { d[n as usize] = x - w / z; } e[n as usize - 1] = 0.0; e[n as usize] = 0.0; x = h[[n as usize, n as usize - 1]]; s = (x).abs() + (z).abs(); p = x / s; q = z / s; r = (p * p + q * q).sqrt(); p = p / r; q = q / r; // Row modification let mut j = n - 1; while j < nn as i16 { z = h[[n as usize - 1, j as usize]]; h[[n as usize - 1, j as usize]] = q * z + p * h[[n as usize, j as usize]]; h[[n as usize, j as usize]] = q * h[[n as usize, j as usize]] - p * z; j = j + 1; } // Column modification let mut i = 0; while i <= n { z = h[[i as usize, n as usize - 1]]; h[[i as usize, n as usize - 1]] = q * z + p * h[[i as usize, n as usize]]; h[[i as usize, n as usize]] = q * h[[i as usize, n as usize]] - p * z; i = i + 1; } // Accumulate transformations let mut i = low; while i <= high { z = v[[i as usize, n as usize - 1]]; v[[i as usize, n as usize - 1]] = q * z + p * v[[i as usize, n as usize]]; v[[i as usize, n as usize]] = q * v[[i as usize, n as usize]] - p * z; i = i + 1; } // Complex pair } else { d[n as usize - 1] = x + p; d[n as usize] = x + p; e[n as usize - 1] = z; e[n as usize] = -z; } n = n - 2; iter = 0; // No convergence yet } else { // Form shift x = h[[n as usize, n as usize]]; y = 0.0; w = 0.0; if l < n { y = h[[n as usize - 1, n as usize - 1]]; w = h[[n as usize, n as usize - 1]] * h[[n as usize - 1, n as usize]]; } // Wilkinson's original ad hoc shift if iter == 10 { exshift += x; let mut i = low; while i <= n as usize { h[[i, i]] -= x; i = i + 1; } s = (h[[n as usize, n as usize - 1]]).abs() + (h[[n as usize - 1, n as usize - 2]]).abs(); y = 0.75 * s; x = y; w = -0.4375 * s * s; } // MATLAB's new ad hoc shift if iter == 30 { s = (y - x) / 2.0; s = s * s + w; if s > 0. { s = s.sqrt(); if y < x { s = -s; } s = x - w / ((y - x) / 2.0 + s); let mut i = low; while i <= n as usize { h[[i, i]] -= s; i = i + 1; } exshift += s; x = 0.964; y = x; w = y; } } iter = iter + 1; // (Could check iteration count here.) // Look for two consecutive small sub-diagonal elements let mut m = n - 2; while m >= l { z = h[[m as usize, m as usize]]; r = x - z; s = y - z; p = (r * s - w) / h[[m as usize + 1, m as usize]] + h[[m as usize, m as usize + 1]]; q = h[[m as usize + 1, m as usize + 1]] - z - r - s; r = h[[m as usize + 2, m as usize + 1]]; s = (p).abs() + (q).abs() + (r).abs(); p = p / s; q = q / s; r = r / s; if m == l { break; } if h[[m as usize, m as usize - 1]].abs() * (q).abs() + (r).abs() < eps * ((p).abs() * ((h[[m as usize - 1, m as usize - 1]]).abs() + (z).abs() + (h[[m as usize + 1, m as usize + 1]]).abs())) { break; } m = m - 1; } let mut i = m + 2; while i <= n { h[[i as usize, i as usize - 2]] = 0.0; if i > m + 2 { h[[i as usize, i as usize - 3]] = 0.0; } i = i + 1; } // Double QR step involving rows l:n and columns m:n let mut k = m; while k <= n - 1 { let notlast = if k != n - 1 { true } else { false }; if k != m { p = h[[k as usize, k as usize - 1]]; q = h[[k as usize + 1, k as usize - 1]]; r = if notlast { h[[k as usize + 2, k as usize - 1]] } else { 0.0 }; x = (p).abs() + (q).abs() + (r).abs(); if x == 0.0 { k = k + 1; continue; } p = p / x; q = q / x; r = r / x; } s = (p * p + q * q + r * r).sqrt(); if p < 0. { s = -s; } if s != 0. { if k != m { h[[k as usize, k as usize - 1]] = -s * x; } else if l != m { h[[k as usize, k as usize - 1]] = -h[[k as usize, k as usize - 1]]; } p = p + s; x = p / s; y = q / s; z = r / s; q = q / p; r = r / p; // Row modification let mut j = k; while j < nn as i16 { p = h[[k as usize, j as usize]] + q * h[[k as usize + 1, j as usize]]; if notlast { p = p + r * h[[k as usize + 2, j as usize]]; h[[k as usize + 2, j as usize]] = h[[k as usize + 2, j as usize]] - p * z; } h[[k as usize, j as usize]] = h[[k as usize, j as usize]] - p * x; h[[k as usize + 1, j as usize]] = h[[k as usize + 1, j as usize]] - p * y; j = j + 1; } // Column modification let mut i = 0; while i <= cmp::min(n as usize, k as usize + 3) { p = x * h[[i, k as usize]] + y * h[[i as usize, k as usize + 1]]; if notlast { p = p + z * h[[i, k as usize + 2]]; h[[i, k as usize + 2]] = h[[i, k as usize + 2]] - p * r; } h[[i, k as usize]] = h[[i, k as usize]] - p; h[[i, k as usize + 1]] = h[[i, k as usize + 1]] - p * q; i = i + 1; } // Accumulate transformations let mut i = low; while i <= high { p = x * v[[i, k as usize]] + y * v[[i, k as usize + 1]]; if notlast { p = p + z * v[[i as usize, k as usize + 2]]; v[[i as usize, k as usize + 2]] = v[[i as usize, k as usize + 2]] - p * r; } v[[i, k as usize]] = v[[i, k as usize]] - p; v[[i, k as usize + 1]] = v[[i, k as usize + 1]] - p * q; i = i + 1; } } // (s != 0) k = k + 1; } // k loop } // check convergence } // while n >= low // Backsubstitute to find vectors of upper triangular form if norm == 0.0 { return; } n = nn as i16 - 1; while n >= 0 { p = d[n as usize]; q = e[n as usize]; // Real vector if q == 0. { let mut l = n; h[[n as usize, n as usize]] = 1.0; let mut i = n as i16 - 1; while i >= 0 { w = h[[i as usize, i as usize]] - p; r = 0.0; let mut j = l; while j <= n { r = r + h[[i as usize, j as usize]] * h[[j as usize, n as usize]]; j = j + 1; } if e[i as usize] < 0.0 { z = w; s = r; } else { l = i; if e[i as usize] == 0.0 { if w != 0.0 { h[[i as usize, n as usize]] = -r / w; } else { h[[i as usize, n as usize]] = -r / (eps * norm); } // Solve real equations } else { x = h[[i as usize, i as usize + 1]]; y = h[[i as usize + 1, i as usize]]; q = (d[i as usize] - p) * (d[i as usize] - p) + e[i as usize] * e[i as usize]; t = (x * s - z * r) / q; h[[i as usize, n as usize]] = t; if (x).abs() > (z).abs() { h[[i as usize + 1, n as usize]] = (-r - w * t) / x; } else { h[[i as usize + 1, n as usize]] = (-s - y * t) / z; } } // Overflow control t = h[[i as usize, n as usize]]; if (eps * t).abs() * t > 1. { let mut j = i; while j <= n as i16 { h[[j as usize, n as usize]] = h[[j as usize, n as usize]] / t; j = j + 1; } } } i = i - 1; } // Complex vector } else if q < 0. { let mut l = n - 1; // Last vector component imaginary so matrix is triangular if (h[[n as usize, n as usize - 1]]).abs() > (h[[n as usize - 1, n as usize]]).abs() { h[[n as usize - 1, n as usize - 1]] = q / h[[n as usize, n as usize - 1]]; h[[n as usize - 1, n as usize]] = -(h[[n as usize, n as usize]] - p) / h[[n as usize, n as usize - 1]]; } else { let (cdivr, cdivi) = cdiv( 0.0, -h[[n as usize - 1, n as usize]], h[[n as usize - 1, n as usize - 1]] - p, q, ); h[[n as usize - 1, n as usize - 1]] = cdivr; h[[n as usize - 1, n as usize]] = cdivi; } h[[n as usize, n as usize - 1]] = 0.0; h[[n as usize, n as usize]] = 1.0; let mut i = n - 2; while i >= 0 { let mut ra = 0.; let mut sa = 0.; let mut vr; let vi; let mut j = l; while j <= n { ra = ra + h[[i as usize, j as usize]] * h[[j as usize, n as usize - 1]]; sa = sa + h[[i as usize, j as usize]] * h[[j as usize, n as usize]]; j = j + 1; } w = h[[i as usize, i as usize]] - p; if e[i as usize] < 0.0 { z = w; r = ra; s = sa; } else { l = i; if e[i as usize] == 0. { let (cdivr, cdivi) = cdiv(-ra, -sa, w, q); h[[i as usize, n as usize - 1]] = cdivr; h[[i as usize, n as usize]] = cdivi; } else { // Solve complex equations x = h[[i as usize, i as usize + 1]]; y = h[[i as usize + 1, i as usize]]; vr = (d[i as usize] - p) * (d[i as usize] - p) + e[i as usize] * e[i as usize] - q * q; vi = (d[i as usize] - p) * 2.0 * q; if vr == 0.0 && vi == 0.0 { vr = eps * norm * ((w).abs() + (q).abs() + (x).abs() + (y).abs() + (z)).abs(); } let (cdivr, cdivi) = cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi); h[[i as usize, n as usize - 1]] = cdivr; h[[i as usize, n as usize]] = cdivi; if (x).abs() > ((z).abs() + (q).abs()) { h[[i as usize + 1, n as usize - 1]] = (-ra - w * h[[i as usize, n as usize - 1]] + q * h[[i as usize, n as usize]]) / x; h[[i as usize + 1, n as usize]] = (-sa - w * h[[i as usize, n as usize]] - q * h[[i as usize, n as usize - 1]]) / x; } else { let (cdivr, cdivi) = cdiv( -r - y * h[[i as usize, n as usize - 1]], -s - y * h[[i as usize, n as usize]], z, q, ); h[[i as usize + 1, n as usize - 1]] = cdivr; h[[i as usize + 1, n as usize]] = cdivi; } } // Overflow control t = (h[[i as usize, n as usize - 1]]).abs().max(h[[i as usize, n as usize]].abs()); if (eps * t) * t > 1. { let mut j = i; while j <= n { j = j + 1; h[[j as usize, n as usize - 1]] = h[[j as usize, n as usize - 1]] / t; h[[j as usize, n as usize]] = h[[j as usize, n as usize]] / t; } } } i = i - 1; } } n = n - 1; } // Vectors of isolated roots let mut i = 0; while i < nn { if i < low || i > high { let mut j = i; while j < nn { v[[i, j]] = h[[i, j]]; j = j + 1; } } i = i + 1; } // Back transformation to get eigenvectors of original matrix let mut j = nn as i16 - 1; while j >= low as i16 { let mut i = low; while i <= high { z = 0.0; let mut k = low; while k <= cmp::min(j as usize, high) { z = z + v[[i, k]] * h[[k, j as usize]]; k = k + 1; } v[[i, j as usize]] = z; i = i + 1; } j = j - 1; } } // This is derived from the Algol procedures orthes and ortran, // by Martin and Wilkinson, Handbook for Auto. Comp., // Vol.ii-Linear Algebra, and the corresponding // Fortran subroutines in EISPACK. #[allow(dead_code)] pub fn orthes(m: &mut Matrix, h_mat: &mut Matrix, v_mat: &mut Matrix) { let low = 0; let n = m.n; let high = n - 1; let mut m = low + 1; let mut ort = vec![0.; n]; while m < high - 1 { // Scale column. let mut scale = 0.0; let mut i = m; //for (int i = m; i < = high; i + +) while i <= high { scale = scale + (h_mat[[i, m - 1]]).abs(); i = i + 1; } if scale != 0.0 { // Compute Householder transformation. let mut h = 0.0; let mut i = high; while i >= m { ort[i] = h_mat[[i, m - 1]] / scale; h += ort[i] * ort[i]; i = i - 1; } let mut g = h.sqrt(); if ort[m] > 0. { g = -g; } h = h - ort[m] * g; ort[m] = ort[m] - g; // Apply Householder similarity transformation // H = (I-u*u'/h)*H*(I-u*u')/h) let mut j = m; while j < n { let mut f = 0.0; let mut i = high; while i >= m { f += ort[i] * h_mat[[i, j]]; i = i - 1; } f = f / h; let mut i = m; while i <= high { h_mat[[i, j]] -= f * ort[i]; i = i + 1; } j = j + 1; } let mut i = 0; while i <= high { let mut f = 0.0; let mut j = high; while j >= m { f += ort[j] * h_mat[[i, j]]; j = j - 1; } f = f / h; let mut j = m; while j <= high { h_mat[[i, j]] -= f * ort[j]; j = j + 1; } i = i + 1; } ort[m] = scale * ort[m]; h_mat[[m, m - 1]] = scale * g; } m = m + 1; } // Accumulate transformations (Algol's ortran). for i in 0..n { for j in 0..n { v_mat[[i, j]] = if i == j { 1.0 } else { 0.0 }; } } let mut m = high - 1; while m >= low + 1 { if h_mat[[m, m - 1]] != 0.0 { let mut i = m + 1; while i <= high { ort[i] = h_mat[[i, m - 1]]; i = i + 1; } let mut j = m; while j <= high { let mut g = 0.0; let mut i = m; while i <= high { g += ort[i] * v_mat[[i, j]]; i = i + 1; } // Double division avoids possible underflow g = (g / ort[m]) / h_mat[[m, m - 1]]; let mut i = m; while i <= high { v_mat[[i, j]] += g * ort[i]; i = i + 1; } j = j + 1; } } m = m - 1; } } fn calc_eigen(m: &mut Matrix) -> Vec<(f64, f64)> { let n = m.n; let mut h_mat = Matrix::new(n); let mut v_mat = Matrix::new(n); let mut d = vec![0.; n]; let mut e = vec![0.; n]; for i in 0..n { for j in 0..n { h_mat[[i, j]] = m[[i, j]]; } } orthes(m, &mut h_mat, &mut v_mat); hqr2(n, &mut h_mat, &mut v_mat, &mut d, &mut e); let mut r = vec![(0., 0.); n]; for i in 0..n { r[i] = (d[i], e[i]) } r } /// Find all roots of the normalized polynomial by finding eigen numbers of the corresponding matrix. /// (Converted from Java by stiv-yakovenko) /// /// Note that found roots are approximate and not sorted. /// /// # Examples /// /// ``` /// use roots::find_roots_eigen; /// /// let roots = find_roots_eigen(vec!(0f64, -1f64, 0f64)); /// // Returns [0f64, 0.9999999999999999f64, -0.9999999999999999f64] while 'x^3 - x = 0' has roots -1, 0, and 1 /// ``` pub fn find_roots_eigen(c: Vec) -> VecDeque { let n = c.len(); let mut m = Matrix::new(n); for i in 0..(n - 1) { m[[i + 1, i]] = 1.; } for i in 0..(n) { m[[i, n - 1]] = -c[i]; } let ei = calc_eigen(&mut m); let mut r = VecDeque::new(); for c in ei { if c.1 * c.1 == 0. { r.push_back(c.0); } } r } #[cfg(test)] mod test { use super::super::super::*; #[test] fn test_find_roots_eigen() { let roots = find_roots_eigen(vec![0f64, -1f64, 0f64]); assert_eq!(roots[0], 0f64); assert_eq!(roots[1], 0.9999999999999999f64); assert_eq!(roots[2], -0.9999999999999999f64); } #[test] fn test_find_roots_eigen_huge_discriminant() { // Try to find roots of the normalized cubic polynomial where the highest coefficient was very small // (as reported by Andrew Hunter in July 2019) let roots = find_roots_eigen(vec![ 0.0126298310280606f64 / -0.000000000000000040410628481035f64, -0.100896606408756f64 / -0.000000000000000040410628481035f64, 0.0689539597036461f64 / -0.000000000000000040410628481035f64, ]); // (According to Wolfram Alpha, roots must be 0.7547108770537f64, 7.23404258961f64, 312537357195213f64) // This means that this function cannot handle such huge numbers. assert_eq!(roots[0], 0f64); assert_eq!(roots[1], 1.5f64); assert_eq!(roots[2], 1706332276816893f64); } #[test] fn test_find_roots_eigen_tim_lueke() { // Try to find roots of the normalized quartic polynomial where the discriminant must be 0 // (as reported by Tim Lueke in December 2019) let roots = dbg!(find_roots_eigen(vec![ -3.75f64 / -14.0625f64, 29.75f64 / -14.0625f64, 4.0f64 / -14.0625f64, -16.0f64 / -14.0625f64, ])); // (According to Wolfram Alpha, roots must be -1.1016116464173349f64, 0.9682783130840016f64) // This means that this function cannot handle such small discriminant. // But at least it finds the right number of them. assert_eq!(roots[0], 0.9990584398692597f64); assert_eq!(roots[1], 0.12511486301943303f64); } } roots-0.0.8/src/numerical/inverse_quadratic.rs000064400000000000000000000176120072674642500176620ustar 00000000000000// Copyright (c) 2019, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::find_roots_quadratic; use super::super::FloatType; use super::Convergency; use super::Interval; use super::Sample; use super::SearchError; /// Definition of the quadratic equation a*x^2 + b*x + c #[derive(Debug, PartialEq)] pub struct Parabola where F: FloatType, { a: F, b: F, c: F, } impl Parabola where F: FloatType, { /// Restore coefficients of the quadratic equation by 3 points fn from_three_points(p1: &Sample, p2: &Sample, p3: &Sample) -> Self { let denom = (p1.x - p2.x) * (p1.x - p3.x) * (p2.x - p3.x); let a = (p3.x * (p2.y - p1.y) + p2.x * (p1.y - p3.y) + p1.x * (p3.y - p2.y)) / denom; let b = (p1.x * p1.x * (p2.y - p3.y) + p3.x * p3.x * (p1.y - p2.y) + p2.x * p2.x * (p3.y - p1.y)) / denom; let c = (p2.x * p2.x * (p3.x * p1.y - p1.x * p3.y) + p2.x * (p1.x * p1.x * p3.y - p3.x * p3.x * p1.y) + p1.x * p3.x * (p3.x - p1.x) * p2.y) / denom; Parabola { a: a, b: b, c: c } } } /// Find a root of the function f(x) = 0 using inverse quadratic approximation. /// /// Pro /// /// + Faster than linear approximation /// + No need for derivative function /// /// Contra /// /// - sqrt is calculated on every step /// - only works for polynomial-like functions /// /// # Failures /// ## NoBracketing /// Initial values do not bracket the root. /// ## NoConvergency /// Algorithm cannot find a root within the given number of iterations. /// # Examples /// /// ``` /// use roots::SimpleConvergency; /// use roots::find_root_inverse_quadratic; /// /// let f = |x| { 1f64*x*x - 1f64 }; /// let mut convergency = SimpleConvergency { eps:1e-15f64, max_iter:30 }; /// /// let root1 = find_root_inverse_quadratic(10f64, 0f64, &f, &mut convergency); /// // Returns approximately Ok(1); /// /// let root2 = find_root_inverse_quadratic(-10f64, 0f64, &f, &mut 1e-15f64); /// // Returns approximately Ok(-1); /// ``` pub fn find_root_inverse_quadratic(a: F, b: F, mut f: Func, convergency: &mut dyn Convergency) -> Result where F: FloatType, Func: FnMut(F) -> F, { let (x1, x2) = if a > b { (b, a) } else { (a, b) }; let sample1 = Sample { x: x1, y: f(x1) }; if convergency.is_root_found(sample1.y) { return Ok(sample1.x); } let sample2 = Sample { x: x2, y: f(x2) }; if convergency.is_root_found(sample2.y) { return Ok(sample2.x); } if !sample1.is_bracketed_with(&sample2) { return Err(SearchError::NoBracketing); } // Initially, find x3 using the regula falsi method let mut interval = Interval { begin: sample1, end: sample2, }; let mut x3 = interval.middle(); if interval.is_converged(convergency) { return Ok(x3); } let mut sample3 = Sample { x: x3, y: f(x3) }; if convergency.is_root_found(sample3.y) { return Ok(sample3.x); } // Iterate quadratically let mut iter = 0; loop { let parabola = Parabola::from_three_points(&interval.begin, &interval.end, &sample3); // Find the new approximation quadratically x3 = if let Some(root) = find_roots_quadratic(parabola.a, parabola.b, parabola.c) .as_ref() .iter() .find(|x| interval.contains_x(x)) { *root } else { // no roots inside interval, fallback to linear approximation interval.middle() }; // calculate the approximated value sample3 = Sample { x: x3, y: f(x3) }; if convergency.is_root_found(sample3.y) { return Ok(sample3.x); } // Narrow down the search interval while keeping the root bracketed if sample3.is_bracketed_with(&interval.begin) { interval.end = Sample { x: sample3.x, y: sample3.y, }; } else { interval.begin = Sample { x: sample3.x, y: sample3.y, }; } if interval.is_converged(convergency) { return Ok(interval.middle()); } iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { return Err(SearchError::NoConvergency); } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn test_find_root_inverse_quadratic() { let f = |x| 1f64 * x * x - 1f64; let mut conv = debug_convergency::DebugConvergency::new(1e-15f64, 30); conv.reset(); assert_float_eq!( 1e-15f64, find_root_inverse_quadratic(10f64, 0f64, &f, &mut conv).ok().unwrap(), 1f64 ); assert_eq!(0, conv.get_iter_count()); conv.reset(); assert_float_eq!( 1e-15f64, find_root_inverse_quadratic(-10f64, 0f64, &f, &mut conv).ok().unwrap(), -1f64 ); assert_eq!(0, conv.get_iter_count()); conv.reset(); assert_eq!( find_root_inverse_quadratic(10f64, 20f64, &f, &mut conv), Err(SearchError::NoBracketing) ); let result = find_root_inverse_quadratic(10f64, 20f64, &f, &mut conv); assert_eq!(result.unwrap_err().to_string(), "Bracketing Error"); assert_eq!(0, conv.get_iter_count()); } #[test] fn test_from_three_points() { assert_eq!( Parabola { a: 1f64, b: 0f64, c: -1f64 }, Parabola::from_three_points( &Sample { x: -10f64, y: 99f64 }, &Sample { x: -2f64, y: 3f64 }, &Sample { x: 0f64, y: -1f64 } ) ); assert_eq!( Parabola { a: 1f64, b: 0f64, c: -1f64 }, Parabola::from_three_points( &Sample { x: 10f64, y: 99f64 }, &Sample { x: 2f64, y: 3f64 }, &Sample { x: 0f64, y: -1f64 } ) ); assert_eq!( Parabola { a: 1f64, b: 0f64, c: -1f64 }, Parabola::from_three_points( &Sample { x: -3f64, y: 8f64 }, &Sample { x: 2f64, y: 3f64 }, &Sample { x: 0f64, y: -1f64 } ) ); } } roots-0.0.8/src/numerical/mod.rs000064400000000000000000000221050072674642500147220ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::FloatType; use std::error::Error; use std::fmt; /// Pair of the independent variable x and the function value y=F(x) #[derive(Debug, PartialEq)] pub struct Sample where F: FloatType, { /// Value of the independent variable (X-axis) x: F, /// Value of the dependent variable (Y-axis) y: F, } impl Sample where F: FloatType, { fn is_bracketed_with(&self, other: &Self) -> bool { self.y * other.y <= F::zero() } } /// Interval between two samples, including these samples #[derive(Debug, PartialEq)] pub struct Interval where F: FloatType, { /// First sample begin: Sample, /// Last sample end: Sample, } impl Interval where F: FloatType, { fn is_bracketed(&self) -> bool { self.begin.is_bracketed_with(&self.end) } fn is_converged(&self, convergency: &mut dyn Convergency) -> bool { convergency.is_converged(self.begin.x, self.end.x) } /// Check if the given X is inside the interval fn contains_x(&self, x: &F) -> bool { *x <= self.end.x && *x >= self.begin.x } /// Returns a point somewhere in middle of the interval for narrowing this interval down. /// Rules are as follows: /// * If the interval is bracketed, use the secant to find the middle point. /// ** The middle point may not be too close to either range of the interval. /// * If the interval is not bracketed (why would one use an unbracketed interval?), bisect it. fn middle(&self) -> F { let _2 = F::from(2i16); let _26 = F::from(26i16); let _27 = F::from(27i16); if self.is_bracketed() && self.begin.y != self.end.y { let mut shift = -self.begin.y * (self.end.x - self.begin.x) / (self.end.y - self.begin.y); if shift < (self.end.x - self.begin.x) / _27 { shift = (self.end.x - self.begin.x) / _27; } if shift > (self.end.x - self.begin.x) * _26 / _27 { shift = (self.end.x - self.begin.x) * _26 / _27; } self.begin.x + shift } else { (self.begin.x + self.end.x) / _2 } } } /// Possible errors #[derive(Debug, PartialEq)] pub enum SearchError { /// The algorithm could not converge within the given number of iterations NoConvergency, /// Initial values do not bracket zero NoBracketing, /// The algorithm cannot continue from the point where the derivative is zero ZeroDerivative, } impl fmt::Display for SearchError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SearchError::NoConvergency => write!(f, "Convergency Error"), SearchError::NoBracketing => write!(f, "Bracketing Error"), SearchError::ZeroDerivative => write!(f, "Zero Derivative Error"), } } } impl Error for SearchError { fn description(&self) -> &str { match self { SearchError::NoConvergency => "The algorithm could not converge within the given number of iterations", SearchError::NoBracketing => "Initial values do not bracket zero", SearchError::ZeroDerivative => "The algorithm cannot continue from the point where the derivative is zero", } } } /// The way to check if the algorithm has finished by either finding a root /// or reaching the iteration limit. pub trait Convergency { /// Return true if the given Y value is close enough to the zero fn is_root_found(&mut self, y: F) -> bool; /// Return true if given x values are close enough to each other fn is_converged(&mut self, x1: F, x2: F) -> bool; /// Return true if no more iterations desired fn is_iteration_limit_reached(&mut self, iter: usize) -> bool; } impl Convergency for F { /// Return true if the given Y value is close enough to the zero fn is_root_found(&mut self, y: F) -> bool { y.abs() < self.abs() } /// Return true if given x values are close enough to each other fn is_converged(&mut self, x1: F, x2: F) -> bool { (x1 - x2).abs() < self.abs() } /// Return true if no more iterations desired fn is_iteration_limit_reached(&mut self, iter: usize) -> bool { iter >= 30 } } pub mod brent; pub mod eigen; pub mod inverse_quadratic; pub mod newton_raphson; pub mod polynom; pub mod regula_falsi; pub mod secant; pub mod debug_convergency; pub mod simple_convergency; #[cfg(test)] mod test { use super::*; #[test] fn sample_bracketed() { let sample1 = Sample { x: 0f64, y: 0f64 }; let sample2 = Sample { x: 1f64, y: 1f64 }; let sample3 = Sample { x: 1f64, y: -1f64 }; let sample4 = Sample { x: -1f64, y: 0f64 }; let sample5 = Sample { x: -1f64, y: 1f64 }; assert_eq!(true, sample1.is_bracketed_with(&sample2)); assert_eq!(true, sample1.is_bracketed_with(&sample3)); assert_eq!(true, sample1.is_bracketed_with(&sample4)); assert_eq!(true, sample1.is_bracketed_with(&sample5)); assert_eq!(true, sample2.is_bracketed_with(&sample3)); assert_eq!(true, sample2.is_bracketed_with(&sample4)); assert_eq!(false, sample2.is_bracketed_with(&sample5)); assert_eq!(true, sample3.is_bracketed_with(&sample4)); assert_eq!(true, sample3.is_bracketed_with(&sample5)); assert_eq!(true, sample4.is_bracketed_with(&sample5)); } #[test] fn root_interval_bracketed() { let sut1 = Interval { begin: Sample { x: 0f64, y: 0f64 }, end: Sample { x: 0f64, y: 0f64 }, }; let sut2 = Interval { begin: Sample { x: 0f32, y: 0f32 }, end: Sample { x: 1f32, y: 0f32 }, }; let sut3 = Interval { begin: Sample { x: 0f64, y: 0f64 }, end: Sample { x: 0f64, y: 1f64 }, }; let sut4 = Interval { begin: Sample { x: -1f64, y: 0f64 }, end: Sample { x: 0f64, y: 0f64 }, }; let sut5 = Interval { begin: Sample { x: -1f64, y: 0f64 }, end: Sample { x: 0f64, y: 1f64 }, }; let sut6 = Interval { begin: Sample { x: -1f32, y: -1f32 }, end: Sample { x: 0f32, y: 1f32 }, }; let sut7 = Interval { begin: Sample { x: 0f64, y: 1f64 }, end: Sample { x: 1f64, y: -1f64 }, }; assert_eq!(true, sut1.is_bracketed()); assert_eq!(true, sut2.is_bracketed()); assert_eq!(true, sut3.is_bracketed()); assert_eq!(true, sut4.is_bracketed()); assert_eq!(true, sut5.is_bracketed()); assert_eq!(true, sut6.is_bracketed()); assert_eq!(true, sut7.is_bracketed()); } #[test] fn root_interval_not_bracketed() { let sut1 = Interval { begin: Sample { x: 0f64, y: 1f64 }, end: Sample { x: 1f64, y: 1f64 }, }; let sut2 = Interval { begin: Sample { x: -1f64, y: -1f64 }, end: Sample { x: 1f64, y: -1f64 }, }; assert_eq!(false, sut1.is_bracketed()); assert_eq!(false, sut2.is_bracketed()); } #[test] fn root_interval_middle() { let sut1 = Interval { begin: Sample { x: 0f64, y: 1f64 }, end: Sample { x: 2f64, y: -3f64 }, }; let sut2 = Interval { begin: Sample { x: -1f64, y: 0f64 }, end: Sample { x: 1f64, y: 0f64 }, }; assert_eq!(0.5f64, sut1.middle()); assert_eq!(0f64, sut2.middle()); } } roots-0.0.8/src/numerical/newton_raphson.rs000064400000000000000000000105500072674642500172100ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::Convergency; use super::SearchError; /// Find a root of the function f(x) = 0 using the Newton-Raphson method. /// /// Pro /// /// + Simple /// + Fast convergency for well-behaved functions /// + No need for initial bracketing /// /// Contra /// /// - Needs derivative function /// - Impossible to predict which root will be found when many roots exist /// - Unstable convergency for non-trivial functions /// - Cannot continue when derivative is zero /// /// # Failures /// ## ZeroDerivative /// The stationary point of the function is encountered. Algorithm cannot continue. /// ## NoConvergency /// Algorithm cannot find a root within the given number of iterations. /// # Examples /// ``` /// use roots::SimpleConvergency; /// use roots::find_root_newton_raphson; /// /// let f = |x| { 1f64*x*x - 1f64 }; /// let d = |x| { 2f64*x }; /// let mut convergency = SimpleConvergency { eps:1e-15f64, max_iter:30 }; /// /// let root1 = find_root_newton_raphson(10f64, &f, &d, &mut convergency); /// // Returns approximately Ok(1); /// /// let root2 = find_root_newton_raphson(-10f64, &f, &d, &mut 1e-15f64); /// // Returns approximately Ok(-1); /// ``` pub fn find_root_newton_raphson( start: F, mut f: Func, mut d: Deriv, convergency: &mut dyn Convergency, ) -> Result where F: FloatType, Func: FnMut(F) -> F, Deriv: FnMut(F) -> F, { let mut x = start; let mut iter = 0; loop { let f = f(x); let d = d(x); if convergency.is_root_found(f) { return Ok(x); } // Derivative is 0; try to correct the bad starting point if convergency.is_root_found(d) { if iter == 0 { x = x + F::one(); iter = iter + 1; continue; } else { return Err(SearchError::ZeroDerivative); } } let x1 = x - f / d; if convergency.is_converged(x, x1) { return Ok(x1); } x = x1; iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { return Err(SearchError::NoConvergency); } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn test_find_root_newton_raphson() { let f = |x| 1f64 * x * x - 1f64; let d = |x| 2f64 * x; let mut conv = debug_convergency::DebugConvergency::new(1e-15f64, 30); conv.reset(); assert_float_eq!( 1e-15f64, find_root_newton_raphson(10f64, &f, &d, &mut conv).ok().unwrap(), 1f64 ); assert_eq!(8, conv.get_iter_count()); conv.reset(); assert_float_eq!( 1e-15f64, find_root_newton_raphson(-10f64, &f, &d, &mut conv).ok().unwrap(), -1f64 ); assert_eq!(8, conv.get_iter_count()); } } roots-0.0.8/src/numerical/polynom.rs000064400000000000000000000477250072674642500156570ustar 00000000000000// Copyright (c) 2017, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::find_roots_cubic; use super::super::find_roots_linear; use super::super::find_roots_quadratic; use super::super::FloatType; use super::Convergency; use super::Interval; use super::Sample; use super::SearchError; #[derive(Debug, PartialEq)] struct ValueAndDerivative where F: FloatType, { value: Sample, derivative: F, } trait Polynom where F: FloatType, { fn value(&self, x: &F) -> F; fn value_and_derivative(&self, x: &F) -> ValueAndDerivative; fn find_root(&self, bracketed_start: &mut Interval, convergency: &mut dyn Convergency) -> Result; fn derivative_polynom(&self) -> Vec; fn to_string(&self) -> String; } impl Polynom for [F] where F: FloatType, { fn value(&self, x: &F) -> F { let mut result = F::zero(); let mut xn = F::one(); // Sum starting with a0 for a in self.iter().rev() { result = result + *a * xn; xn = xn * *x; } // The highest coefficient of the normalized polynom is 1 result + xn } fn value_and_derivative(&self, x: &F) -> ValueAndDerivative { let mut xn = F::one(); // x^n for SUM(A(n)*x^(n)) let mut value = F::zero(); let mut xn1 = F::zero(); // x^n-1 for SUM(n*A(n-1)*x^(n-1)) let mut derivative = F::zero(); let mut n = F::zero(); // Sum starting with a0 for a in self.iter().rev() { value = value + *a * xn; derivative = derivative + *a * n * xn1; xn1 = xn; xn = xn * *x; n = n + F::one(); } // The highest coefficient of the normalized polynom is 1 ValueAndDerivative { value: Sample { x: *x, y: value + xn }, derivative: derivative + n * xn1, } } fn find_root(&self, bracketed_start: &mut Interval, convergency: &mut dyn Convergency) -> Result { if bracketed_start.is_bracketed() { let interval = bracketed_start; let mut iter = 0; loop { if convergency.is_root_found(interval.begin.y) { break Ok(interval.begin.x); } else if convergency.is_root_found(interval.end.y) { break Ok(interval.end.x); } else if interval.is_converged(convergency) { break Ok(interval.middle()); } else { let middle = self.value_and_derivative(&interval.middle()); let next_sample = if middle.derivative != F::zero() { let newton_raphson = middle.value.x - middle.value.y / middle.derivative; if newton_raphson >= interval.begin.x && newton_raphson <= interval.end.x { let newton_raphson_value = self.value(&newton_raphson); if newton_raphson_value.abs() < middle.value.y.abs() { Sample { x: newton_raphson, y: newton_raphson_value, } } else { middle.value } } else { middle.value } } else { middle.value }; if interval.begin.is_bracketed_with(&next_sample) { interval.end = Sample { x: next_sample.x, y: next_sample.y, }; } else { interval.begin = Sample { x: next_sample.x, y: next_sample.y, }; } } iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { break Err(SearchError::NoConvergency); } } } else { Err(SearchError::NoBracketing) } } fn derivative_polynom(&self) -> Vec { let mut result = Vec::from(self); result.truncate(self.len() - 1); let n: F = F::from(self.len() as i16); let mut ni = F::one(); for x in result.iter_mut().rev() { *x = (*x * ni) / n; ni = ni + F::one(); } result } fn to_string(&self) -> String { let mut result = String::new(); let mut p = self.len(); if self.len() == 0 { result.push_str("x=0") } else { result.push_str(&format!("x^{:?}", p)); for x in self.iter() { p = p - 1; if *x != F::zero() { if *x > F::zero() { result.push_str(&format!("+{:?}*x^{:?}", *x, p)); } else { result.push_str(&format!("-{:?}*x^{:?}", -*x, p)); } } } } result } } /// Interval for searching roots enum SearchInterval where F: FloatType, { /// [-infinity .. +infinity] Whole, /// [-infinity .. x] First(Sample), /// [x .. +infinity ] Last(Sample), /// [x1 .. x2 ] Middle(Interval), } enum BracketingDirection { TowardsPositive, TowardsNegative, } fn initial_bracket( initial_sample: &Sample, direction: &BracketingDirection, polynom: &[F], derivative_polynom: &[F], convergency: &mut dyn Convergency, ) -> Result, SearchError> where F: FloatType, { let mut iter = 0; let towards_positive = match direction { &BracketingDirection::TowardsPositive => true, &BracketingDirection::TowardsNegative => false, }; let mut step = if towards_positive { F::one() } else { -F::one() }; let initial_copy = Sample { x: initial_sample.x, y: initial_sample.y, }; let mut next_x = initial_sample.x + step; let result = loop { let mut next_y = polynom.value(&next_x); let mut next_sample = Sample { x: next_x, y: next_y }; if next_sample.is_bracketed_with(&initial_sample) { break Ok(if towards_positive { Interval { begin: initial_copy, end: next_sample, } } else { Interval { begin: next_sample, end: initial_copy, } }); } else { let derivative = derivative_polynom.value(&next_x); if derivative > F::zero() { next_x = next_x - next_y / derivative; next_y = polynom.value(&next_x); next_sample = Sample { x: next_x, y: next_y }; if next_sample.is_bracketed_with(&initial_sample) { break Ok(if towards_positive { Interval { begin: initial_copy, end: next_sample, } } else { Interval { begin: next_sample, end: initial_copy, } }); } }; step = step * F::from(2i16); next_x = next_x + step; iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { break Err(SearchError::NoConvergency); }; } }; result } fn narrow_down( initial_interval: &SearchInterval, polynom: &[F], derivative_polynom: &[F], convergency: &mut dyn Convergency, ) -> Result, SearchError> where F: FloatType, { match initial_interval { &SearchInterval::Whole => { let zero_sample = Sample { x: F::zero(), y: polynom.value(&F::zero()), }; let zero_interval = if zero_sample.y > F::zero() { SearchInterval::First(zero_sample) } else { SearchInterval::Last(zero_sample) }; narrow_down(&zero_interval, polynom, derivative_polynom, convergency) } &SearchInterval::First(ref end) => initial_bracket( &end, &BracketingDirection::TowardsNegative, polynom, derivative_polynom, convergency, ), &SearchInterval::Last(ref begin) => initial_bracket( &begin, &BracketingDirection::TowardsPositive, polynom, derivative_polynom, convergency, ), &SearchInterval::Middle(ref interval) => { if interval.is_bracketed() { let middle_x = if interval.begin.y == interval.end.y { (interval.begin.x + interval.end.x) / F::from(2i16) } else { interval.begin.x - interval.begin.y * (interval.end.x - interval.begin.x) / (interval.end.y - interval.begin.y) }; let mut middle_sample = Sample { x: middle_x, y: polynom.value(&middle_x), }; let derivative = derivative_polynom.value(&middle_x); if derivative != F::zero() { let closer_x = middle_sample.x - middle_sample.y / derivative; if closer_x >= interval.begin.x && closer_x <= interval.end.x { middle_sample = Sample { x: closer_x, y: polynom.value(&closer_x), }; } } if interval.begin.is_bracketed_with(&middle_sample) { Ok(Interval { begin: Sample { x: interval.begin.x, y: interval.begin.y, }, end: middle_sample, }) } else { Ok(Interval { begin: middle_sample, end: Sample { x: interval.end.x, y: interval.end.y, }, }) } } else { Err(SearchError::NoBracketing) } } } } fn find_root_intervals( polynom: &[F], derivative_polynom: &[F], convergency: &mut dyn Convergency, ) -> Result>, SearchError> where F: FloatType, { let mut result = Vec::new(); let derivative_roots = find_roots_sturm(&derivative_polynom, convergency); let symmetric_polynom = polynom.len() % 2 == 0; let mut expect_positive = !symmetric_polynom; let mut previous_interval: SearchInterval = SearchInterval::Whole; // Iterate through all roots of the derivative polynom for derivative_root in derivative_roots.iter().filter_map(|s| match s { &Ok(ref x) => Some(x), &Err(_) => None, }) { let value = polynom.value(derivative_root); if (expect_positive && value >= F::zero()) || (!expect_positive && value < F::zero()) { // Transition found let interval_to_add = match &previous_interval { &SearchInterval::Whole => SearchInterval::First(Sample { x: *derivative_root, y: value, }), &SearchInterval::First(ref previous_end) => SearchInterval::Middle(Interval { begin: Sample { x: previous_end.x, y: previous_end.y, }, end: Sample { x: *derivative_root, y: value, }, }), _ => panic!("Unexpected type of the previous root interval!"), }; result.push(interval_to_add); expect_positive = !expect_positive; } previous_interval = SearchInterval::First(Sample { x: *derivative_root, y: value, }); } // All roots are checked, now the final step match previous_interval { SearchInterval::Whole => { if !symmetric_polynom { result.push(SearchInterval::Whole); } Ok(result) } SearchInterval::First(sample) => { if sample.x < F::zero() { result.push(SearchInterval::Last(sample)); } Ok(result) } _ => Err(SearchError::NoBracketing), } } /// Find all roots of the normalized polynomial /// x^n + a[0]*x^(n-1) + a[1]*x^(n-2) + ... + a[n-1] = 0 /// using the Sturm's theorem recursively. /// /// # Examples /// /// ``` /// use roots::find_roots_sturm; /// /// let polynom = &[1f64,1f64,1f64,1f64,1f64,1f64]; /// /// let roots_or_errors = find_roots_sturm(polynom, &mut 1e-6); /// // Returns vector of roots or search errors; /// /// let roots: Vec<_> = find_roots_sturm(polynom, &mut 1e-8f64) /// .iter() /// .filter_map(|s| match s { /// &Ok(ref x) => Some(*x), /// &Err(_) => None, /// }) /// .collect(); /// // Returns vector of roots filterin out all search errors; /// ``` pub fn find_roots_sturm(a: &[F], convergency: &mut dyn Convergency) -> Vec> where F: FloatType, { match a.len() { 0 => Vec::new(), 1 => find_roots_linear(F::one(), a[0]).as_ref().iter().map(|s| Ok(*s)).collect(), 2 => find_roots_quadratic(F::one(), a[0], a[1]) .as_ref() .iter() .map(|s| Ok(*s)) .collect(), 3 => find_roots_cubic(F::one(), a[0], a[1], a[2]) .as_ref() .iter() .map(|s| Ok(*s)) .collect(), _ => { let mut result = Vec::new(); let derivative_polynom = a.derivative_polynom(); match find_root_intervals(a, &derivative_polynom, convergency) { Ok(root_intervals) => { for root_interval in &root_intervals { if let Ok(mut narrowed) = narrow_down(&root_interval, a, &derivative_polynom, convergency) { result.push(a.find_root(&mut narrowed, convergency)); } } } Err(error) => { result.push(Err(error)); } } result } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn test_find_roots_sturm() { let polynom = &[-2f64, 1f64]; let roots = find_roots_sturm(polynom, &mut 1e-6f64); assert_eq!(roots, [Ok(1f64)]); } #[test] fn test_polynom_value() { let polynom = [1f64, -2f64, 1f64]; assert_eq!(1f64, polynom.value(&0f64)); assert_eq!(1f64, polynom.value(&1f64)); assert_eq!(3f64, polynom.value(&-1f64)); } #[test] fn test_polynom_value_and_derivative() { let polynom = [1f64, -2f64, 1f64]; assert_eq!( ValueAndDerivative { value: Sample { x: 0f64, y: 1f64 }, derivative: -2f64 }, polynom.value_and_derivative(&0f64) ); assert_eq!( ValueAndDerivative { value: Sample { x: 1f64, y: 1f64 }, derivative: 3f64 }, polynom.value_and_derivative(&1f64) ); assert_eq!( ValueAndDerivative { value: Sample { x: -1f64, y: 3f64 }, derivative: -1f64 }, polynom.value_and_derivative(&-1f64) ); } #[test] fn test_derivative_polynom_3() { // x^3 + 1*x^2 - 2*x^1 + 1*x^0 => 3*x^2 + 2*x^1 - 2*x^0 => x^2 + (2/3)*x^1 - (2/3)*x^0 let polynom = [1f64, -2f64, 1f64]; let derivative = polynom.derivative_polynom(); assert_float_array_eq!(1e-15, derivative, [2f64 / 3f64, -2f64 / 3f64]); } #[test] fn test_derivative_polynom_5() { // x^5 - 2*x^4 - 3*x^3 + 4*x^2 + 0*x^1 + 0*x^0 => 5*x^4 - 8*x^3 - 9*x^2 + 8*x^1 + 0*x^0 => x^4 - (8/5)*x^3 - (9/5)*x^2 + (8/5)*x^1 + 0*x^0 let polynom = [-2f64, -3f64, 4f64, 0f64, 0f64]; let derivative = polynom.derivative_polynom(); assert_float_array_eq!(1e-15, derivative, [-8f64 / 5f64, -9f64 / 5f64, 8f64 / 5f64, 0f64]); } #[test] fn find_roots_sturm_7() { // x^7+4.0*x^6-4.0*x^4+2.0*x^3+1.0*x^2+6.0*x^1-3.0*x^0 => {-3.6547, -1.67175, 0.455904} let polynom = [4f64, 0f64, -4f64, 2f64, 1f64, 6f64, -3f64]; let roots: Vec<_> = find_roots_sturm(&polynom, &mut 1e-8f64) .iter() .filter_map(|s| match s { &Ok(ref x) => Some(*x), &Err(_) => None, }) .collect(); assert_float_array_eq!(1e-5, roots, [-3.6547f64, -1.67175f64, 0.455904f64]); } #[test] fn find_roots_sturm_tim_lueke() { // Try to find roots of the normalized quartic polynomial where the discriminant must be 0 // (as reported by Tim Lueke in December 2019) // -14.0625*x^4-3.75*x^3+29.75*x^2+4.0*x^1-16.0*x^0 => {-1.1016116464173349, 0.9682783130840016} let polynom = [ -3.75f64 / -14.0625f64, 29.75f64 / -14.0625f64, 4.0f64 / -14.0625f64, -16.0f64 / -14.0625f64, ]; let roots: Vec<_> = find_roots_sturm(&polynom, &mut 1e-8f64) .iter() .filter_map(|s| match s { &Ok(ref x) => Some(*x), &Err(_) => None, }) .collect(); // these roots cannot be found assert_float_array_eq!(1e-5, roots, []); //assert_float_array_eq!(1e-5, roots, [-1.1016116464173349f64, 0.9682783130840016f64]); } } roots-0.0.8/src/numerical/regula_falsi.rs000064400000000000000000000121600072674642500166000ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::Convergency; use super::SearchError; /// Illinois modification to the classical method #[derive(Debug, PartialEq)] enum Edge { /// Value is close to X1, reduce the Y1 weight EdgeX1, /// Value is in the middle of the interval NoEdge, /// Value is close to X2, reduce the Y2 weight EdgeX2, } /// Find a root of the function f(x) = 0 using the Illinois modification of the regula falsi method. /// /// Pro /// /// + Simple /// + Robust /// + No need for derivative function /// /// Contra /// /// - Slow /// - Needs initial bracketing /// /// # Failures /// ## NoBracketing /// Initial values do not bracket the root. /// ## NoConvergency /// Algorithm cannot find a root within the given number of iterations. /// # Examples /// /// ``` /// use roots::SimpleConvergency; /// use roots::find_root_regula_falsi; /// /// let f = |x| { 1f64*x*x - 1f64 }; /// let mut convergency = SimpleConvergency { eps:1e-15f64, max_iter:30 }; /// /// let root1 = find_root_regula_falsi(10f64, 0f64, &f, &mut convergency); /// // Returns approximately Ok(1); /// /// let root2 = find_root_regula_falsi(-10f64, 0f64, &f, &mut 1e-15f64); /// // Returns approximately Ok(-1); /// ``` pub fn find_root_regula_falsi(a: F, b: F, mut f: Func, convergency: &mut dyn Convergency) -> Result where F: FloatType, Func: FnMut(F) -> F, { let _2 = F::from(2i16); let (mut x1, mut x2) = if a > b { (b, a) } else { (a, b) }; let mut y1 = f(x1); if convergency.is_root_found(y1) { return Ok(x1); } let mut y2 = f(x2); if convergency.is_root_found(y2) { return Ok(x2); } if y1 * y2 > F::zero() { return Err(SearchError::NoBracketing); } let mut edge = Edge::NoEdge; let mut iter = 0; loop { let x = (x1 * y2 - x2 * y1) / (y2 - y1); if convergency.is_converged(x1, x2) { return Ok(x); } let y = f(x); if convergency.is_root_found(y) { return Ok(x); } if y * y1 > F::zero() { x1 = x; y1 = y; if edge == Edge::EdgeX1 { y2 = y2 / _2; } edge = Edge::EdgeX1; } else if y * y2 > F::zero() { x2 = x; y2 = y; if edge == Edge::EdgeX2 { y1 = y1 / _2; } edge = Edge::EdgeX2; } else { return Ok(x); } iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { return Err(SearchError::NoConvergency); } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn test_find_root_regula_falsi() { let f = |x| 1f64 * x * x - 1f64; let mut conv = debug_convergency::DebugConvergency::new(1e-15f64, 30); conv.reset(); assert_float_eq!( 1e-15f64, find_root_regula_falsi(10f64, 0f64, &f, &mut conv).ok().unwrap(), 1f64 ); assert_eq!(11, conv.get_iter_count()); conv.reset(); assert_float_eq!( 1e-15f64, find_root_regula_falsi(-10f64, 0f64, &f, &mut conv).ok().unwrap(), -1f64 ); assert_eq!(11, conv.get_iter_count()); conv.reset(); assert_eq!( find_root_regula_falsi(10f64, 20f64, &f, &mut conv), Err(SearchError::NoBracketing) ); let result = find_root_regula_falsi(10f64, 20f64, &f, &mut conv); assert_eq!(result.unwrap_err().to_string(), "Bracketing Error"); assert_eq!(0, conv.get_iter_count()); } } roots-0.0.8/src/numerical/secant.rs000064400000000000000000000104750072674642500154270ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::Convergency; use super::SearchError; /// Find a root of the function f(x) = 0 using the secant method. /// /// Pro /// /// + Simple /// + No need for initial bracketing /// + No need for derivative function /// /// Contra /// /// - Impossible to predict which root will be found when many roots exist /// - Unstable convergency for non-trivial functions /// - Cannot continue when two consecutive iterations have the same value /// /// # Failures /// ## ZeroDerivative /// Two consecutive points have the same value. Algorithm cannot continue. /// ## NoConvergency /// Algorithm cannot find a root within the given number of iterations. /// # Examples /// /// ``` /// use roots::SimpleConvergency; /// use roots::find_root_secant; /// /// let f = |x| { 1f64*x*x - 1f64 }; /// let mut convergency = SimpleConvergency { eps:1e-15f64, max_iter:30 }; /// /// let root1 = find_root_secant(10f64, 0f64, &f, &mut convergency); /// // Returns approximately Ok(1); /// /// let root2 = find_root_secant(-10f64, 0f64, &f, &mut 1e-15f64); /// // Returns approximately Ok(-1); /// ``` pub fn find_root_secant(first: F, second: F, mut f: Func, convergency: &mut dyn Convergency) -> Result where F: FloatType, Func: FnMut(F) -> F, { let mut x1 = first; let mut y1 = f(x1); if convergency.is_root_found(y1) { return Ok(x1); } let mut x2 = second; let mut y2 = f(x2); if convergency.is_root_found(y2) { return Ok(x2); } let mut iter = 0; loop { if convergency.is_root_found(y1 - y2) { return Err(SearchError::ZeroDerivative); } let x = x2 - y2 * (x2 - x1) / (y2 - y1); if convergency.is_converged(x, x2) { return Ok(x); } let y = f(x); if convergency.is_root_found(y) { return Ok(x); } x1 = x2; y1 = y2; x2 = x; y2 = y; iter = iter + 1; if convergency.is_iteration_limit_reached(iter) { return Err(SearchError::NoConvergency); } } } #[cfg(test)] mod test { use super::super::*; use super::*; #[test] fn test_find_root_secant() { let f = |x| 1f64 * x * x - 1f64; let mut conv = debug_convergency::DebugConvergency::new(1e-15f64, 30); conv.reset(); assert_float_eq!(1e-15f64, find_root_secant(10f64, 0f64, &f, &mut conv).ok().unwrap(), 1f64); assert_eq!(12, conv.get_iter_count()); conv.reset(); assert_float_eq!(1e-15f64, find_root_secant(-10f64, 0f64, &f, &mut conv).ok().unwrap(), -1f64); assert_eq!(12, conv.get_iter_count()); conv.reset(); assert_eq!( find_root_secant(10f64, -10f64, &f, &mut conv), Err(SearchError::ZeroDerivative) ); assert_eq!(0, conv.get_iter_count()); } } roots-0.0.8/src/numerical/simple_convergency.rs000064400000000000000000000040610072674642500200370ustar 00000000000000// Copyright (c) 2015, Mikhail Vorotilov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::super::FloatType; use super::Convergency; /// A very basic convergency rules that must be sufficient for many cases. /// The absolute precision is the same for x and y axes, no relative precision. pub struct SimpleConvergency { /// Precision for both X and Y axes pub eps: F, /// Maximum number of iterations pub max_iter: usize, } impl Convergency for SimpleConvergency { fn is_root_found(&mut self, y: F) -> bool { y.abs() < self.eps.abs() } fn is_converged(&mut self, x1: F, x2: F) -> bool { (x1 - x2).abs() < self.eps.abs() } fn is_iteration_limit_reached(&mut self, iter: usize) -> bool { iter >= self.max_iter } }