From b4140bf2ec18f6743cb90cba7c67d26b03018757 Mon Sep 17 00:00:00 2001
From: "Thomas J. Kennedy" <tkennedy@cs.odu.edu>
Date: Sat, 20 Jun 2020 13:40:41 -0400
Subject: [PATCH] Port Python Non-Linear Solvers to Rust

---
 .../{ => Python}/run_solvers.py               |   0
 .../{ => Python}/solvers.py                   |   0
 NonLinearEquationSolvers/Rust/Cargo.lock      | 149 +++++++++++++
 NonLinearEquationSolvers/Rust/Cargo.toml      |  10 +
 NonLinearEquationSolvers/Rust/rustfmt.toml    |  20 ++
 NonLinearEquationSolvers/Rust/src/lib.rs      |   5 +
 .../Rust/src/loop_monoliths.rs                | 196 ++++++++++++++++++
 NonLinearEquationSolvers/Rust/src/main.rs     | 159 ++++++++++++++
 8 files changed, 539 insertions(+)
 rename NonLinearEquationSolvers/{ => Python}/run_solvers.py (100%)
 rename NonLinearEquationSolvers/{ => Python}/solvers.py (100%)
 create mode 100644 NonLinearEquationSolvers/Rust/Cargo.lock
 create mode 100644 NonLinearEquationSolvers/Rust/Cargo.toml
 create mode 100644 NonLinearEquationSolvers/Rust/rustfmt.toml
 create mode 100644 NonLinearEquationSolvers/Rust/src/lib.rs
 create mode 100644 NonLinearEquationSolvers/Rust/src/loop_monoliths.rs
 create mode 100644 NonLinearEquationSolvers/Rust/src/main.rs

diff --git a/NonLinearEquationSolvers/run_solvers.py b/NonLinearEquationSolvers/Python/run_solvers.py
similarity index 100%
rename from NonLinearEquationSolvers/run_solvers.py
rename to NonLinearEquationSolvers/Python/run_solvers.py
diff --git a/NonLinearEquationSolvers/solvers.py b/NonLinearEquationSolvers/Python/solvers.py
similarity index 100%
rename from NonLinearEquationSolvers/solvers.py
rename to NonLinearEquationSolvers/Python/solvers.py
diff --git a/NonLinearEquationSolvers/Rust/Cargo.lock b/NonLinearEquationSolvers/Rust/Cargo.lock
new file mode 100644
index 0000000..0305a80
--- /dev/null
+++ b/NonLinearEquationSolvers/Rust/Cargo.lock
@@ -0,0 +1,149 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+
+[[package]]
+name = "hamcrest2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f837c62de05dc9cc71ff6486cd85de8856a330395ae338a04bfcefe5e91075"
+dependencies = [
+ "num",
+ "regex",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "memchr"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+
+[[package]]
+name = "nonlinear_equation_solvers"
+version = "0.1.0"
+dependencies = [
+ "hamcrest2",
+]
+
+[[package]]
+name = "num"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "regex"
+version = "1.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
+
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
diff --git a/NonLinearEquationSolvers/Rust/Cargo.toml b/NonLinearEquationSolvers/Rust/Cargo.toml
new file mode 100644
index 0000000..0452cdd
--- /dev/null
+++ b/NonLinearEquationSolvers/Rust/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "nonlinear_equation_solvers"
+version = "0.1.0"
+authors = ["Thomas J. Kennedy <tkennedy@cs.odu.edu>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dev-dependencies]
+hamcrest2 = "*"
diff --git a/NonLinearEquationSolvers/Rust/rustfmt.toml b/NonLinearEquationSolvers/Rust/rustfmt.toml
new file mode 100644
index 0000000..cab6f59
--- /dev/null
+++ b/NonLinearEquationSolvers/Rust/rustfmt.toml
@@ -0,0 +1,20 @@
+# Commented out options are only available in the 'nightly' channel
+
+# control_brace_style = "ClosingNextLine"
+# merge_imports = true
+newline_style = "Unix"
+# normalize_comments = true
+remove_nested_parens = true
+# reorder_impl_items = true
+reorder_imports = false
+# trailing_semicolon = true
+
+tab_spaces = 4
+hard_tabs = false
+max_width = 80
+# wrap_comments = true
+# report_todo = "Always"
+# report_fixme = "Always"
+# space_after_colon = true
+
+# blank_lines_lower_bound = 1
diff --git a/NonLinearEquationSolvers/Rust/src/lib.rs b/NonLinearEquationSolvers/Rust/src/lib.rs
new file mode 100644
index 0000000..027ffb5
--- /dev/null
+++ b/NonLinearEquationSolvers/Rust/src/lib.rs
@@ -0,0 +1,5 @@
+#[cfg(test)]
+#[macro_use]
+extern crate hamcrest2;
+
+pub mod loop_monoliths;
diff --git a/NonLinearEquationSolvers/Rust/src/loop_monoliths.rs b/NonLinearEquationSolvers/Rust/src/loop_monoliths.rs
new file mode 100644
index 0000000..9a39f01
--- /dev/null
+++ b/NonLinearEquationSolvers/Rust/src/loop_monoliths.rs
@@ -0,0 +1,196 @@
+const EPSILON: f64 = 10e-8;
+const MAX_ITERATIONS: u64 = 100;
+const ONE_HALF: f64 = 0.5;
+
+#[derive(Debug, Clone)]
+pub struct InvariantError {
+    pub msg: String,
+}
+
+impl InvariantError {
+    fn new(msg: String) -> Self {
+        InvariantError { msg }
+    }
+}
+
+/// Compute a solution to f using the bisection method
+///
+/// a0 = a
+/// b0 = b
+///
+/// for n = 1, 2, ... do
+/// xn = 0.5 (a_{n-1} + b_{n-1});
+///     if f(xn) < 0 then
+///         an = xn, bn = bn-1
+///     end if;
+///
+///     if f(xn) > 0 then
+///         an = an-1, bn = xn
+///     end if;
+///
+///     if bn - an < eps then
+///         break;
+///     end if;
+///
+/// end for
+pub fn bisection(
+    f: &impl Fn(f64) -> f64,
+    a: f64,
+    b: f64,
+) -> Result<(u64, f64), InvariantError> {
+    let mut a_n = a;
+    let mut b_n = b;
+
+    let mut x_n = 0_f64;
+
+    for n in 1..MAX_ITERATIONS {
+        if f(b_n) < 0.0 {
+            return Err(InvariantError::new(format!(
+                "$f(b_{} = {}) < 0$",
+                (n - 1),
+                b_n
+            )));
+        }
+
+        x_n = ONE_HALF * (a_n + b_n);
+
+        let f_of_x_n = f(x_n);
+
+        if f_of_x_n < 0.0 {
+            a_n = x_n;
+            // b_n = b_n-1 # unchanged
+        }
+
+        if f_of_x_n >= 0.0 {
+            // a_n = a_n-1 # unchanged
+            b_n = x_n;
+        }
+
+        // Stop Condition
+        if (b_n - a_n).abs() < EPSILON {
+            return Ok((n, x_n));
+        }
+    }
+
+    Ok((MAX_ITERATIONS, x_n))
+}
+
+/// Compute a solution to f using the false position method
+///
+/// a0 = a
+/// b0 = b;
+///
+/// for n = 1, 2, ... do
+///     xn = an - ((an - bn)/(f(an) - f(bn)))f(an);
+///
+///     if f(xn) · f(an) > 0 then
+///         an+1 = xn, bn+1 = bn
+///     else
+///         an+1 = an, bn+1 = xn
+///     end if
+/// end for
+pub fn regula_falsi(
+    f: &impl Fn(f64) -> f64,
+    a: f64,
+    b: f64,
+) -> Result<(u64, f64), InvariantError> {
+    let mut a_n = a;
+    let mut b_n = b;
+
+    let mut x_n = 0_f64;
+
+    for n in 1..MAX_ITERATIONS {
+        x_n = a_n - ((a_n - b_n) / (f(a_n) - f(b_n))) * f(a_n);
+
+        if !x_n.is_finite() {
+            return Err(InvariantError::new(
+                format!("$f(a_n) - f(b_n) == 0$",),
+            ));
+        }
+
+        if f(x_n) * f(a_n) > 0.0 {
+            a_n = x_n;
+        // b_n - No change
+        } else {
+            // a_n - No Change
+            b_n = x_n;
+        }
+
+        if (b_n - a_n).abs() < EPSILON {
+            return Ok((n, x_n));
+        }
+    }
+
+    Ok((MAX_ITERATIONS, x_n))
+}
+
+/// Compute a solution to f using the secant method
+///
+/// x_{n-1} = a0 = a
+/// x_n = b0 = b;
+///
+/// for n = 1, 2, ... do
+///     xn = xn - ((xn - x_n-1)/(f(xn) - f(x_n-1)))f(xn);
+///
+///     if f(xn) · f(an) > 0 then
+///         an+1 = xn, bn+1 = bn
+///     else
+///         an+1 = an, bn+1 = xn
+///     end if
+/// end for
+pub fn secant(
+    f: &impl Fn(f64) -> f64,
+    x_n_minus_1_in: f64,
+    x_n_in: f64,
+) -> Result<(u64, f64), InvariantError> {
+    let mut x_n_minus_1 = x_n_minus_1_in;
+    let mut x_n = x_n_in;
+
+    for n in 2..MAX_ITERATIONS {
+        let next_x_n =
+            x_n - ((x_n - x_n_minus_1) / (f(x_n) - f(x_n_minus_1))) * f(x_n);
+
+        if !next_x_n.is_finite() {
+            return Err(InvariantError::new(format!(
+                "$f(x_n) - f(x_nm1) == 0$",
+            )));
+        }
+
+        x_n_minus_1 = x_n;
+        x_n = next_x_n;
+
+        if (x_n - x_n_minus_1).abs() < EPSILON {
+            return Ok((n, x_n));
+        }
+    }
+
+    Ok((MAX_ITERATIONS, x_n))
+}
+
+/// Compute a solution to f using Newton's method.
+///
+/// Compute x_{n+1} = x_n - (f(x_n) / df(x_n)) until
+/// |x_{n+1} - x_n| <= eps
+pub fn newton(
+    f: &impl Fn(f64) -> f64,
+    df: &impl Fn(f64) -> f64,
+    x_0: f64,
+) -> Result<(u64, f64), InvariantError> {
+    let mut x_n = x_0;
+
+    for n in 1..MAX_ITERATIONS {
+        let next_x_n = x_n - (f(x_n) / df(x_n));
+
+        if !next_x_n.is_finite() {
+            return Err(InvariantError::new(format!("$df(x_n) == 0$")));
+        }
+
+        if (x_n - next_x_n).abs() < EPSILON {
+            return Ok((n, x_n));
+        }
+
+        x_n = next_x_n;
+    }
+
+    Ok((MAX_ITERATIONS, x_n))
+}
diff --git a/NonLinearEquationSolvers/Rust/src/main.rs b/NonLinearEquationSolvers/Rust/src/main.rs
new file mode 100644
index 0000000..f2c91c3
--- /dev/null
+++ b/NonLinearEquationSolvers/Rust/src/main.rs
@@ -0,0 +1,159 @@
+use std::env;
+use nonlinear_equation_solvers::loop_monoliths;
+
+fn main() {
+    let (a, b) = __handle_cli_args();
+
+    let (math_f, math_df) = __build_f_df();
+
+    println!("# Bisection");
+    match loop_monoliths::bisection(&math_f, a, b) {
+        Ok((_num_iter, solution)) => {
+            let fx = math_f(solution);
+
+            print_solution(solution, fx);
+        }
+        Err(err) => {
+            println!();
+            println!("## Method Failed");
+            println!("{:?}", err);
+        }
+    }
+
+    println!();
+    println!("# Regula Falsi (False Position)");
+    match loop_monoliths::regula_falsi(&math_f, a, b) {
+        Ok((_num_iter, solution)) => {
+            let fx = math_f(solution);
+
+            print_solution(solution, fx);
+        }
+        Err(err) => {
+            println!();
+            println!("## Method Failed");
+            println!("{:?}", err);
+        }
+    }
+
+    println!();
+    println!("# Secant");
+    match loop_monoliths::secant(&math_f, a, b) {
+        Ok((_num_iter, solution)) => {
+            let fx = math_f(solution);
+
+            print_solution(solution, fx);
+        }
+        Err(err) => {
+            println!();
+            println!("## Method Failed");
+            println!("{:?}", err);
+        }
+    }
+
+    println!();
+    println!("# Newton's Method");
+    match loop_monoliths::newton(&math_f, &math_df, a) {
+        Ok((_num_iter, solution)) => {
+            let fx = math_f(solution);
+
+            print_solution(solution, fx);
+        }
+        Err(err) => {
+            println!();
+            println!("## Method Failed");
+            println!("{:?}", err);
+        }
+    }
+}
+
+/// Handle positional command line argument validation.
+///
+/// # Return
+///     Tuple in the form (lower limit, upper limit)
+fn __handle_cli_args() -> (f64, f64) {
+    let args: Vec<String> = env::args().collect();
+
+    if args.len() < 3 {
+        eprintln!("Usage:\n    {} a b", args[0]);
+        std::process::exit(1);
+    }
+
+    let limit_a = args[1]
+        .parse::<f64>()
+        .unwrap_or_else(|_err| std::process::exit(2));
+
+    let limit_b = args[2]
+        .parse::<f64>()
+        .unwrap_or_else(|_err| std::process::exit(3));
+
+    (limit_a, limit_b)
+}
+
+/// Print the solution (x) and f(x) under a subsection heading.
+///
+/// :param solution: approximate solution
+/// :param fx_solution: f(solution)
+fn print_solution(solution: f64, fx_solution: f64) {
+    println!();
+    println!("## Solution");
+    println!("$x={:20.8}", solution);
+    println!();
+    println!("$f(x)={:20.8}", fx_solution);
+    println!();
+}
+
+/// Wrapper function that returns two math style functions:
+///
+///   - f - a function in the form Real -> Real
+///   - df - the derivative of f in the form Real -> Real
+///
+/// A where clause does not work...
+///
+/// <https://stackoverflow.com/questions/47514930/what-are-the-differences-between-an-impl-trait-argument-and-generic-function-par>
+///
+/// Rust lambdas/closures syntax is tied with C++'s shenanigans for frustration
+///
+fn __build_f_df() -> (impl Fn(f64) -> f64, impl Fn(f64) -> f64) {
+    let f = |x: f64| -> f64 {
+        // (x ** 2) - 1
+        // Fraction(math.cos(x))
+        // Fraction(math.log(x))  // can not have negative operand (Real)
+        // x**5 - (7 * x)**2
+        x.powf(2.0) - (3.0 * x) - 4.0
+    };
+
+    let df = Box::new(|x: f64| -> f64 {
+        // 2 * x
+        // Fraction(-1 * math.sin(x))
+        // Fraction(numerator=1, denominator=x)
+        // 5 * (x ** 4) - (14 * x)
+        2.0 * x - 3.0
+    });
+
+    (f, df)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use hamcrest2::prelude::*;
+
+    #[test]
+    fn test_math_f() {
+        let (math_f, _) = __build_f_df();
+
+        assert_that!(math_f(0.0), close_to(-4.0, 1e-8));
+        assert_that!(math_f(1.0), close_to(-6.0, 1e-8));
+        assert_that!(math_f(2.0), close_to(-6.0, 1e-8));
+    }
+
+    #[test]
+    fn test_math_df() {
+        let (_, math_df) = __build_f_df();
+
+        assert_that!(math_df(0.0), close_to(-3.0, 1e-8));
+        assert_that!(math_df(1.0), close_to(-1.0, 1e-8));
+        assert_that!(math_df(2.0), close_to(1.0, 1e-8));
+    }
+}
-- 
GitLab