diff --git a/.vscode/settings.json b/.vscode/settings.json index 20a9269e..0c2cb93a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,9 +2,11 @@ "cSpell.words": [ "aiᵢⱼ", "archlinux", + "Arenstorf", "Arioli", "BLACS", "blasint", + "bweuler", "Demmel", "devel", "dgeev", @@ -17,14 +19,23 @@ "dgetri", "dgssvx", "Dᵢⱼₖₗ", + "dopri", + "Dormand", "Dorozhinskii", "dpotrf", "dscal", "dsyev", "dsyrk", "dtype", + "dznrm2", + "FACCON", + "Fehlberg", "Flannery", + "FSAL", + "fweuler", "gfortran", + "Gustafsson", + "Heun", "ifort", "IIIₛ", "ᵢⱼₖₗ", @@ -43,27 +54,36 @@ "lredrhs", "lrhs", "lsol", + "Lubich", "lwork", + "mdeuler", "memcheck", + "Merson", "msgpass", "nelt", + "Nørsett", + "nstage", + "nstep", "odyad", "oneapi", "PERMUT", "PREORDER", "pthread", + "Radau", "RINFOG", "rowcol", "rowcoliter", "rowcolrig", "rustup", "Schur", + "substeps", "Teukolsky", "tgamma", "tocsc", "tocsr", "udyad", "unsym", + "Verner", "Vetterling", "zcopy", "zgemm", @@ -74,6 +94,8 @@ "zgetri", "zherk", "zlange", + "ZMUMPS", + "Zonneveld", "zpotrf", "zscal", "zsyrk" diff --git a/Cargo.toml b/Cargo.toml index 560fb5ac..049093ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [workspace] members = [ - "russell_lab", - "russell_sparse", - "russell_stat", - "russell_tensor" + "russell_lab", + "russell_ode", + "russell_sparse", + "russell_stat", + "russell_tensor", ] resolver = "2" diff --git a/README.md b/README.md index c0121904..0897a2fb 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![Bertrand Russell](Bertrand_Russell_1957.jpg) -([CC0](http://creativecommons.org/publicdomain/zero/1.0/deed.en). Photo: [Bertrand Russell](https://en.wikipedia.org/wiki/Bertrand_Russell)) +([CC0](https://creativecommons.org/publicdomain/zero/1.0/deed.en). Photo: [Bertrand Russell](https://en.wikipedia.org/wiki/Bertrand_Russell)) ## Contents @@ -298,7 +298,7 @@ fn main() -> Result<(), StrError> { // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(1, 0, 3.0)?; @@ -336,7 +336,7 @@ fn main() -> Result<(), StrError> { // analysis let mut stats = StatsLinSol::new(); umfpack.update_stats(&mut stats); - let (mx, ex) = (stats.determinant.mantissa, stats.determinant.exponent); + let (mx, ex) = (stats.determinant.mantissa_real, stats.determinant.exponent); println!("det(a) = {:?}", mx * f64::powf(10.0, ex)); println!("rcond = {:?}", stats.output.umfpack_rcond_estimate); Ok(()) diff --git a/russell_lab/Cargo.toml b/russell_lab/Cargo.toml index 5efb81ef..0be8a2b6 100644 --- a/russell_lab/Cargo.toml +++ b/russell_lab/Cargo.toml @@ -21,7 +21,6 @@ num-traits = "0.2" serde = { version = "1.0", features = ["derive"] } [dev-dependencies] -rmp-serde = "1.1" serde_json = "1.0" [build-dependencies] diff --git a/russell_lab/c_code/interface_blas.c b/russell_lab/c_code/interface_blas.c index c10be4b3..60d956fc 100644 --- a/russell_lab/c_code/interface_blas.c +++ b/russell_lab/c_code/interface_blas.c @@ -14,6 +14,8 @@ #define FN_DGESVD dgesvd_ #define FN_DGETRF dgetrf_ #define FN_DGETRI dgetri_ +#define FN_ZGETRF zgetrf_ +#define FN_ZGETRI zgetri_ #else #include "cblas.h" #include "lapack.h" @@ -28,6 +30,8 @@ #define FN_DGESVD LAPACK_dgesvd #define FN_DGETRF LAPACK_dgetrf #define FN_DGETRI LAPACK_dgetri +#define FN_ZGETRF LAPACK_zgetrf +#define FN_ZGETRI LAPACK_zgetri #endif #include "constants.h" @@ -78,7 +82,7 @@ int32_t c_get_num_threads() { } // Computes the solution to a system of linear equations -// +// void c_dgesv(const int32_t *n, const int32_t *nrhs, double *a, @@ -91,7 +95,7 @@ void c_dgesv(const int32_t *n, } // Computes the solution to a real system of linear equations (complex version) -// +// void c_zgesv(const int32_t *n, const int32_t *nrhs, COMPLEX64 *a, @@ -104,7 +108,7 @@ void c_zgesv(const int32_t *n, } // Computes the matrix norm -// +// double c_dlange(int32_t norm_code, const int32_t *m, const int32_t *n, @@ -119,7 +123,7 @@ double c_dlange(int32_t norm_code, } // Computes the matrix norm (complex version) -// +// double c_zlange(int32_t norm_code, const int32_t *m, const int32_t *n, @@ -134,7 +138,7 @@ double c_zlange(int32_t norm_code, } // Computes the Cholesky factorization of a real symmetric positive definite matrix -// +// void c_dpotrf(C_BOOL upper, const int32_t *n, double *a, @@ -161,7 +165,7 @@ void c_dsyev(C_BOOL calc_v, } // Computes the eigenvalues and eigenvectors of a general matrix -// +// void c_dgeev(C_BOOL calc_vl, C_BOOL calc_vr, const int32_t *n, @@ -182,7 +186,7 @@ void c_dgeev(C_BOOL calc_vl, } // Computes the singular value decomposition (SVD) -// +// void c_dgesvd(int32_t jobu_code, int32_t jobvt_code, const int32_t *m, @@ -209,7 +213,7 @@ void c_dgesvd(int32_t jobu_code, } // Computes the LU factorization of a general (m,n) matrix -// +// void c_dgetrf(const int32_t *m, const int32_t *n, double *a, @@ -220,7 +224,7 @@ void c_dgetrf(const int32_t *m, } // Computes the inverse of a matrix using the LU factorization computed by dgetrf -// +// void c_dgetri(const int32_t *n, double *a, const int32_t *lda, @@ -230,3 +234,26 @@ void c_dgetri(const int32_t *n, int32_t *info) { FN_DGETRI(n, a, lda, ipiv, work, lwork, info); } + +// Computes the LU factorization of a general (m,n) matrix +// +void c_zgetrf(const int32_t *m, + const int32_t *n, + COMPLEX64 *a, + const int32_t *lda, + int32_t *ipiv, + int32_t *info) { + FN_ZGETRF(m, n, a, lda, ipiv, info); +} + +// Computes the inverse of a matrix using the LU factorization computed by zgetrf +// +void c_zgetri(const int32_t *n, + COMPLEX64 *a, + const int32_t *lda, + const int32_t *ipiv, + COMPLEX64 *work, + const int32_t *lwork, + int32_t *info) { + FN_ZGETRI(n, a, lda, ipiv, work, lwork, info); +} diff --git a/russell_lab/examples/ex_matrix_eigenvalues.rs b/russell_lab/examples/ex_matrix_eigenvalues.rs index 81711ba6..b5e8564c 100644 --- a/russell_lab/examples/ex_matrix_eigenvalues.rs +++ b/russell_lab/examples/ex_matrix_eigenvalues.rs @@ -58,8 +58,10 @@ fn main() -> Result<(), StrError> { // err := a⋅v - v⋅λ // ``` let a = ComplexMatrix::from(&data); - let v = complex_mat_zip(&v_real, &v_imag)?; - let d = complex_vec_zip(&l_real, &l_imag)?; + let mut v = ComplexMatrix::new(m, m); + let mut d = ComplexVector::new(m); + complex_mat_zip(&mut v, &v_real, &v_imag)?; + complex_vec_zip(&mut d, &l_real, &l_imag)?; let lam = ComplexMatrix::diagonal(d.as_data()); let mut a_v = ComplexMatrix::new(m, m); let mut v_l = ComplexMatrix::new(m, m); diff --git a/russell_lab/src/base/auxiliary_blas.rs b/russell_lab/src/base/auxiliary_blas.rs index f636810b..41841e60 100644 --- a/russell_lab/src/base/auxiliary_blas.rs +++ b/russell_lab/src/base/auxiliary_blas.rs @@ -6,7 +6,7 @@ extern "C" { fn c_get_num_threads() -> i32; // Finds the index of the maximum absolute value - // + // fn cblas_idamax(n: i32, x: *const f64, incx: i32) -> i32; } diff --git a/russell_lab/src/base/formatters.rs b/russell_lab/src/base/formatters.rs index 579b2475..b44060a1 100644 --- a/russell_lab/src/base/formatters.rs +++ b/russell_lab/src/base/formatters.rs @@ -66,11 +66,31 @@ pub fn format_nanoseconds(nanoseconds: u128) -> String { buf } +/// Formats a number using the scientific notation +pub fn format_scientific(num: f64, width: usize, precision: usize) -> String { + // based on + const EXP_PAD: usize = 2; + let mut result = format!("{:.precision$e}", num, precision = precision); + let exp = result.split_off(result.find('e').unwrap()); + let (sign, exp) = if exp.starts_with("e-") { + ('-', &exp[2..]) + } else { + ('+', &exp[1..]) + }; + result.push_str(&format!("E{}{:0>pad$}", sign, exp, pad = EXP_PAD)); + format!("{:>width$}", result, width = width) +} + +/// Formats a number using the scientific notation as in Fortran with the ES23.15 format +pub fn format_fortran(num: f64) -> String { + format_scientific(num, 23, 15) +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::format_nanoseconds; + use super::{format_fortran, format_nanoseconds, format_scientific}; #[test] fn format_nanoseconds_works() { @@ -170,4 +190,32 @@ mod tests { res = format_nanoseconds(3_601_100_000_000); assert_eq!(res, "1h1.1s"); } + + #[test] + fn format_scientific_works() { + assert_eq!(format_scientific(0.1111, 9, 2), " 1.11E-01"); + assert_eq!(format_scientific(0.02222, 11, 4), " 2.2220E-02"); + assert_eq!(format_scientific(3333.0, 10, 3), " 3.333E+03"); + assert_eq!(format_scientific(-44444.0, 9, 1), " -4.4E+04"); + assert_eq!(format_scientific(0.0, 8, 1), " 0.0E+00"); + assert_eq!(format_scientific(1.0, 23, 15), " 1.000000000000000E+00"); + assert_eq!(format_scientific(42.0, 23, 15), " 4.200000000000000E+01"); + assert_eq!(format_scientific(9999999999.00, 8, 1), " 1.0E+10"); + assert_eq!(format_scientific(999999999999.00, 23, 15), " 9.999999999990000E+11"); + assert_eq!(format_scientific(123456789.1011, 11, 4), " 1.2346E+08"); + } + + #[test] + fn format_fortran_works() { + assert_eq!(format_fortran(0.1111), " 1.111000000000000E-01"); + assert_eq!(format_fortran(0.02222), " 2.222000000000000E-02"); + assert_eq!(format_fortran(3333.0), " 3.333000000000000E+03"); + assert_eq!(format_fortran(-44444.0), " -4.444400000000000E+04"); + assert_eq!(format_fortran(0.0), " 0.000000000000000E+00"); + assert_eq!(format_fortran(1.0), " 1.000000000000000E+00"); + assert_eq!(format_fortran(42.0), " 4.200000000000000E+01"); + assert_eq!(format_fortran(9999999999.00), " 9.999999999000000E+09"); + assert_eq!(format_fortran(999999999999.00), " 9.999999999990000E+11"); + assert_eq!(format_fortran(123456789.1011), " 1.234567891011000E+08"); + } } diff --git a/russell_lab/src/base/stopwatch.rs b/russell_lab/src/base/stopwatch.rs index 1f486d1a..96073838 100644 --- a/russell_lab/src/base/stopwatch.rs +++ b/russell_lab/src/base/stopwatch.rs @@ -17,7 +17,7 @@ use std::time::Instant; /// sleep(Duration::new(0, 1_000)); /// } /// -/// let mut sw = Stopwatch::new("current dt = "); +/// let mut sw = Stopwatch::new(); /// expensive_calculation(); /// sw.stop(); /// println!("{}", sw); @@ -45,7 +45,7 @@ use std::time::Instant; /// } /// /// let mut elapsed_times = vec![0_u128; 3]; -/// let mut sw = Stopwatch::new(""); +/// let mut sw = Stopwatch::new(); /// /// expensive_calculation(); /// elapsed_times[0] = sw.stop_and_reset(); @@ -59,8 +59,8 @@ use std::time::Instant; /// // println!("{:?}", elapsed_times); // will show something like: /// // [57148, 55991, 55299] /// ``` +#[derive(Clone, Copy, Debug)] pub struct Stopwatch { - label: &'static str, initial_time: Instant, final_time: Instant, } @@ -80,13 +80,12 @@ impl Stopwatch { /// /// ``` /// use russell_lab::Stopwatch; - /// let sw = Stopwatch::new("elapsed time = "); - /// assert_eq!(format!("{}", sw), "elapsed time = 0ns"); + /// let sw = Stopwatch::new(); + /// assert_eq!(format!("{}", sw), "0ns"); /// ``` - pub fn new(label: &'static str) -> Self { + pub fn new() -> Self { let now = Instant::now(); Stopwatch { - label, initial_time: now, final_time: now, } @@ -104,7 +103,7 @@ impl Stopwatch { /// use russell_lab::Stopwatch; /// use std::thread::sleep; /// use std::time::Duration; - /// let mut sw = Stopwatch::new(""); + /// let mut sw = Stopwatch::new(); /// sleep(Duration::new(0, 1_000)); /// let elapsed = sw.stop(); /// assert!(elapsed > 0); @@ -124,11 +123,11 @@ impl Stopwatch { /// use russell_lab::Stopwatch; /// use std::thread::sleep; /// use std::time::Duration; - /// let mut sw = Stopwatch::new("delta_t = "); + /// let mut sw = Stopwatch::new(); /// sleep(Duration::new(0, 1_000)); /// sw.stop(); /// sw.reset(); - /// assert_eq!(format!("{}", sw), "delta_t = 0ns"); + /// assert_eq!(format!("{}", sw), "0ns"); /// ``` pub fn reset(&mut self) { let now = Instant::now(); @@ -148,13 +147,13 @@ impl Stopwatch { /// use russell_lab::Stopwatch; /// use std::thread::sleep; /// use std::time::Duration; - /// let mut sw = Stopwatch::new("current = "); + /// let mut sw = Stopwatch::new(); /// sleep(Duration::new(0, 1_000)); /// let elapsed = sw.stop_and_reset(); /// // println!("{}", format_nanoseconds(elapsed)); // will show something like /// // current = 63.099µs /// assert!(elapsed > 0); - /// assert_eq!(format!("{}", sw), "current = 0ns"); + /// assert_eq!(format!("{}", sw), "0ns"); /// ``` pub fn stop_and_reset(&mut self) -> u128 { // calc elapsed @@ -172,7 +171,7 @@ impl Stopwatch { impl fmt::Display for Stopwatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let delta = self.final_time.duration_since(self.initial_time); - write!(f, "{}{}", self.label, format_nanoseconds(delta.as_nanos())).unwrap(); + write!(f, "{}", format_nanoseconds(delta.as_nanos())).unwrap(); Ok(()) } } @@ -185,17 +184,25 @@ mod tests { use std::thread::sleep; use std::time::Duration; + #[test] + fn clone_copy_and_debug_work() { + let sw = Stopwatch::new(); + let copy = sw; + let clone = sw.clone(); + assert_eq!(copy.initial_time, sw.initial_time); + assert_eq!(clone.initial_time, sw.initial_time); + assert!(format!("{:?}", sw).len() > 0); + } + #[test] fn new_and_display_work() { - let sw1 = Stopwatch::new(""); - let sw2 = Stopwatch::new("calculation time = "); + let sw1 = Stopwatch::new(); assert_eq!(format!("{}", sw1), "0ns"); - assert_eq!(format!("{}", sw2), "calculation time = 0ns"); } #[test] fn stop_works() { - let mut sw = Stopwatch::new(""); + let mut sw = Stopwatch::new(); sleep(Duration::new(0, 1_000)); let elapsed = sw.stop(); assert!(elapsed > 0); @@ -203,7 +210,7 @@ mod tests { #[test] fn reset_works() { - let mut sw = Stopwatch::new(""); + let mut sw = Stopwatch::new(); sleep(Duration::new(0, 1_000)); let mut elapsed = sw.stop(); @@ -220,7 +227,7 @@ mod tests { #[test] fn stop_and_reset_works() { - let mut sw = Stopwatch::new(""); + let mut sw = Stopwatch::new(); sleep(Duration::new(0, 1_000)); let elapsed = sw.stop_and_reset(); diff --git a/russell_lab/src/internal/add_arrays.rs b/russell_lab/src/internal/add_arrays.rs index 8a65e7ad..6507cb87 100644 --- a/russell_lab/src/internal/add_arrays.rs +++ b/russell_lab/src/internal/add_arrays.rs @@ -6,7 +6,7 @@ extern "C" { // real ------------------------------------------------------------------------------------------------------ // Computes constant times a vector plus a vector. - // + // fn cblas_daxpy(n: i32, alpha: f64, x: *const f64, incx: i32, y: *mut f64, incy: i32); // Copies a vector into another @@ -14,13 +14,13 @@ extern "C" { fn cblas_dcopy(n: i32, x: *const f64, incx: i32, y: *mut f64, incy: i32); // Scales a vector by a constant - // + // fn cblas_dscal(n: i32, alpha: f64, x: *const f64, incx: i32); // complex --------------------------------------------------------------------------------------------------- // Computes constant times a vector plus a vector (Complex version) - // + // fn cblas_zaxpy(n: i32, alpha: *const Complex64, x: *const Complex64, incx: i32, y: *mut Complex64, incy: i32); // Copies a vector into another (complex version) @@ -28,7 +28,7 @@ extern "C" { fn cblas_zcopy(n: i32, x: *const Complex64, incx: i32, y: *mut Complex64, incy: i32); // Scales a vector by a constant (complex version) - // + // fn cblas_zscal(n: i32, alpha: *const Complex64, x: *mut Complex64, incx: i32); } diff --git a/russell_lab/src/lib.rs b/russell_lab/src/lib.rs index 2d60e8fa..63a20f63 100644 --- a/russell_lab/src/lib.rs +++ b/russell_lab/src/lib.rs @@ -70,7 +70,7 @@ //! } //! ``` -/// Defines a type alias for the error type as a static string +/// Defines the error output as a static string pub type StrError = &'static str; pub mod base; diff --git a/russell_lab/src/matrix/aliases.rs b/russell_lab/src/matrix/aliases.rs index 17a17c1a..a7449ea1 100644 --- a/russell_lab/src/matrix/aliases.rs +++ b/russell_lab/src/matrix/aliases.rs @@ -1,8 +1,8 @@ use crate::NumMatrix; use num_complex::Complex64; -/// Matrix is an alias to NumMatrix<f64> and is used in most functions that call OpenBLAS +/// Defines an alias to NumMatrix with f64 pub type Matrix = NumMatrix; -/// ComplexMatrix is an alias to NumMatrix<Complex64> and is used in most functions that call OpenBLAS +/// Defines an alias to NumMatrix with Complex64 pub type ComplexMatrix = NumMatrix; diff --git a/russell_lab/src/matrix/complex_mat_copy.rs b/russell_lab/src/matrix/complex_mat_copy.rs new file mode 100644 index 00000000..6f8426d5 --- /dev/null +++ b/russell_lab/src/matrix/complex_mat_copy.rs @@ -0,0 +1,95 @@ +use super::ComplexMatrix; +use crate::{to_i32, StrError}; +use num_complex::Complex64; + +extern "C" { + // Copies a vector into another + // + fn cblas_zcopy(n: i32, x: *const Complex64, incx: i32, y: *mut Complex64, incy: i32); +} + +/// Copies complex matrix +/// +/// ```text +/// b := a +/// ``` +/// +/// # Example +/// +/// ``` +/// use num_complex::Complex64; +/// use russell_lab::{cpx, complex_mat_copy, ComplexMatrix, StrError}; +/// +/// fn main() -> Result<(), StrError> { +/// let a = ComplexMatrix::from(&[ +/// [cpx!(1.0, 1.0), cpx!(2.0, -1.0), cpx!(3.0, 1.0)], +/// [cpx!(4.0, 1.0), cpx!(5.0, -1.0), cpx!(6.0, 1.0)], +/// ]); +/// let mut b = ComplexMatrix::from(&[ +/// [cpx!(-1.0, 1.0), cpx!(-2.0, 2.0), cpx!(-3.0, 3.0)], +/// [cpx!(-4.0, 4.0), cpx!(-5.0, 5.0), cpx!(-6.0, 6.0)], +/// ]); +/// complex_mat_copy(&mut b, &a)?; +/// let correct = "┌ ┐\n\ +/// │ 1+1i 2-1i 3+1i │\n\ +/// │ 4+1i 5-1i 6+1i │\n\ +/// └ ┘"; +/// assert_eq!(format!("{}", b), correct); +/// Ok(()) +/// } +/// ``` +pub fn complex_mat_copy(b: &mut ComplexMatrix, a: &ComplexMatrix) -> Result<(), StrError> { + let (m, n) = b.dims(); + if a.nrow() != m || a.ncol() != n { + return Err("matrices are incompatible"); + } + let n_i32: i32 = to_i32(m * n); + unsafe { + cblas_zcopy(n_i32, a.as_data().as_ptr(), 1, b.as_mut_data().as_mut_ptr(), 1); + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::{complex_mat_copy, ComplexMatrix}; + use crate::{complex_mat_approx_eq, cpx}; + use num_complex::Complex64; + + #[test] + fn complex_mat_copy_fails_on_wrong_dimensions() { + let a_2x2 = ComplexMatrix::new(2, 2); + let a_2x1 = ComplexMatrix::new(2, 1); + let a_1x2 = ComplexMatrix::new(1, 2); + let mut b_2x2 = ComplexMatrix::new(2, 2); + let mut b_2x1 = ComplexMatrix::new(2, 1); + let mut b_1x2 = ComplexMatrix::new(1, 2); + assert_eq!(complex_mat_copy(&mut b_2x2, &a_2x1), Err("matrices are incompatible")); + assert_eq!(complex_mat_copy(&mut b_2x2, &a_1x2), Err("matrices are incompatible")); + assert_eq!(complex_mat_copy(&mut b_2x1, &a_2x2), Err("matrices are incompatible")); + assert_eq!(complex_mat_copy(&mut b_1x2, &a_2x2), Err("matrices are incompatible")); + } + + #[test] + fn complex_mat_copy_works() { + #[rustfmt::skip] + let a = ComplexMatrix::from(&[ + [cpx!(10.0, 1.0), cpx!(20.0, 2.0), cpx!(30.0, 3.0)], + [cpx!(40.0, 4.0), cpx!(50.0, 5.0), cpx!(60.0, 6.0)], + ]); + #[rustfmt::skip] + let mut b = ComplexMatrix::from(&[ + [100.0, 200.0, 300.0], + [400.0, 500.0, 600.0], + ]); + complex_mat_copy(&mut b, &a).unwrap(); + #[rustfmt::skip] + let correct = &[ + [cpx!(10.0, 1.0), cpx!(20.0, 2.0), cpx!(30.0, 3.0)], + [cpx!(40.0, 4.0), cpx!(50.0, 5.0), cpx!(60.0, 6.0)], + ]; + complex_mat_approx_eq(&b, correct, 1e-15); + } +} diff --git a/russell_lab/src/matrix/complex_mat_inverse.rs b/russell_lab/src/matrix/complex_mat_inverse.rs new file mode 100644 index 00000000..094d2627 --- /dev/null +++ b/russell_lab/src/matrix/complex_mat_inverse.rs @@ -0,0 +1,494 @@ +use super::{complex_mat_copy, ComplexMatrix}; +use crate::{cpx, to_i32, StrError}; +use num_complex::Complex64; + +extern "C" { + // Computes the LU factorization of a general (m,n) matrix + // + fn c_zgetrf(m: *const i32, n: *const i32, a: *mut Complex64, lda: *const i32, ipiv: *mut i32, info: *mut i32); + + // Computes the inverse of a matrix using the LU factorization computed by zgetrf + // + fn c_zgetri( + n: *const i32, + a: *mut Complex64, + lda: *const i32, + ipiv: *const i32, + work: *mut Complex64, + lwork: *const i32, + info: *mut i32, + ); + +} + +// constants +const ZERO_DETERMINANT_NORM: f64 = 1e-15; + +/// Computes the inverse of a square matrix and returns its determinant +/// +/// ```text +/// ai := a⁻¹ +/// ``` +/// +/// # Output +/// +/// * `ai` -- (m,m) inverse matrix +/// * Returns the matrix determinant +/// +/// # Input +/// +/// * `a` -- (m,m) matrix, symmetric or not +/// +/// # Examples +/// +/// ``` +/// use num_complex::Complex64; +/// use russell_lab::{cpx, complex_mat_inverse, ComplexMatrix, StrError}; +/// +/// fn main() -> Result<(), StrError> { +/// // set matrix +/// let mut a = ComplexMatrix::from(&[ +/// [cpx!(1.0, 0.0), cpx!(2.0, 0.0), cpx!(3.0, 0.0)], +/// [cpx!(0.0, 0.0), cpx!(1.0, 0.0), cpx!(4.0, 0.0)], +/// [cpx!(5.0, 0.0), cpx!(6.0, 0.0), cpx!(0.0, 0.0)], +/// ]); +/// let a_copy = a.clone(); +/// +/// // compute inverse matrix +/// let mut ai = ComplexMatrix::new(3, 3); +/// complex_mat_inverse(&mut ai, &mut a)?; +/// +/// // compare with solution +/// let ai_correct = "┌ ┐\n\ +/// │ -24+0i 18+0i 5+0i │\n\ +/// │ 20+0i -15+0i -4+0i │\n\ +/// │ -5+0i 4+0i 1+0i │\n\ +/// └ ┘"; +/// assert_eq!(format!("{}", ai), ai_correct); +/// +/// // check if a⋅ai == identity +/// let (m, n) = a.dims(); +/// let mut a_ai = ComplexMatrix::new(m, m); +/// for i in 0..m { +/// for j in 0..m { +/// for k in 0..n { +/// a_ai.add(i, j, a_copy.get(i, k) * ai.get(k, j)); +/// } +/// } +/// } +/// let identity = "┌ ┐\n\ +/// │ 1+0i 0+0i 0+0i │\n\ +/// │ 0+0i 1+0i 0+0i │\n\ +/// │ 0+0i 0+0i 1+0i │\n\ +/// └ ┘"; +/// assert_eq!(format!("{}", a_ai), identity); +/// Ok(()) +/// } +/// ``` +pub fn complex_mat_inverse(ai: &mut ComplexMatrix, a: &ComplexMatrix) -> Result { + // check + let (m, n) = a.dims(); + if m != n { + return Err("matrix must be square"); + } + if ai.nrow() != m || ai.ncol() != n { + return Err("matrices are incompatible"); + } + + // handle zero-sized matrix + if m == 0 { + return Ok(Complex64::new(0.0, 0.0)); + } + + // handle small matrix + if m == 1 { + let det = a.get(0, 0); + if det.norm() <= ZERO_DETERMINANT_NORM { + return Err("cannot compute inverse due to zero determinant"); + } + ai.set(0, 0, 1.0 / det); + return Ok(det); + } + + if m == 2 { + let det = a.get(0, 0) * a.get(1, 1) - a.get(0, 1) * a.get(1, 0); + if det.norm() <= ZERO_DETERMINANT_NORM { + return Err("cannot compute inverse due to zero determinant"); + } + ai.set(0, 0, a.get(1, 1) / det); + ai.set(0, 1, -a.get(0, 1) / det); + ai.set(1, 0, -a.get(1, 0) / det); + ai.set(1, 1, a.get(0, 0) / det); + return Ok(det); + } + + if m == 3 { + #[rustfmt::skip] + let det = + a.get(0,0) * (a.get(1,1) * a.get(2,2) - a.get(1,2) * a.get(2,1)) + - a.get(0,1) * (a.get(1,0) * a.get(2,2) - a.get(1,2) * a.get(2,0)) + + a.get(0,2) * (a.get(1,0) * a.get(2,1) - a.get(1,1) * a.get(2,0)); + + if det.norm() <= ZERO_DETERMINANT_NORM { + return Err("cannot compute inverse due to zero determinant"); + } + + ai.set(0, 0, (a.get(1, 1) * a.get(2, 2) - a.get(1, 2) * a.get(2, 1)) / det); + ai.set(0, 1, (a.get(0, 2) * a.get(2, 1) - a.get(0, 1) * a.get(2, 2)) / det); + ai.set(0, 2, (a.get(0, 1) * a.get(1, 2) - a.get(0, 2) * a.get(1, 1)) / det); + + ai.set(1, 0, (a.get(1, 2) * a.get(2, 0) - a.get(1, 0) * a.get(2, 2)) / det); + ai.set(1, 1, (a.get(0, 0) * a.get(2, 2) - a.get(0, 2) * a.get(2, 0)) / det); + ai.set(1, 2, (a.get(0, 2) * a.get(1, 0) - a.get(0, 0) * a.get(1, 2)) / det); + + ai.set(2, 0, (a.get(1, 0) * a.get(2, 1) - a.get(1, 1) * a.get(2, 0)) / det); + ai.set(2, 1, (a.get(0, 1) * a.get(2, 0) - a.get(0, 0) * a.get(2, 1)) / det); + ai.set(2, 2, (a.get(0, 0) * a.get(1, 1) - a.get(0, 1) * a.get(1, 0)) / det); + + return Ok(det); + } + + // copy a into ai + complex_mat_copy(ai, a).unwrap(); + + // compute LU factorization + let m_i32 = to_i32(m); + let lda = m_i32; + let mut ipiv = vec![0; m]; + let mut info = 0; + unsafe { + c_zgetrf( + &m_i32, + &m_i32, + ai.as_mut_data().as_mut_ptr(), + &lda, + ipiv.as_mut_ptr(), + &mut info, + ); + } + if info < 0 { + println!("LAPACK ERROR (dgetrf): Argument #{} had an illegal value", -info); + return Err("LAPACK ERROR (dgetrf): An argument had an illegal value"); + } else if info > 0 { + println!("LAPACK ERROR (dgetrf): U({},{}) is exactly zero", info - 1, info - 1); + return Err( + "LAPACK ERROR (dgetrf): The factorization has been completed, but the factor U is exactly singular", + ); + } + + // first, compute the determinant ai.data from dgetrf + let mut det = cpx!(1.0, 0.0); + for i in 0..m_i32 { + let iu = i as usize; + // NOTE: ipiv are 1-based indices + if ipiv[iu] - 1 == i { + det = det * ai.get(iu, iu); + } else { + det = -det * ai.get(iu, iu); + } + } + // second, perform the inversion + let lwork = m_i32; + let mut work = vec![cpx!(0.0, 0.0); lwork as usize]; + unsafe { + c_zgetri( + &m_i32, + ai.as_mut_data().as_mut_ptr(), + &lda, + ipiv.as_mut_ptr(), + work.as_mut_ptr(), + &lwork, + &mut info, + ); + } + + // done + Ok(det) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::{complex_mat_inverse, ComplexMatrix, ZERO_DETERMINANT_NORM}; + use crate::{complex_approx_eq, complex_mat_approx_eq, cpx}; + use num_complex::Complex64; + + /// Computes a⋅ai that should equal I for a square matrix + fn get_a_times_ai(a: &ComplexMatrix, ai: &ComplexMatrix) -> ComplexMatrix { + let (m, n) = a.dims(); + let mut a_ai = ComplexMatrix::new(m, m); + for i in 0..m { + for j in 0..m { + for k in 0..n { + a_ai.add(i, j, a.get(i, k) * ai.get(k, j)); + } + } + } + a_ai + } + + #[test] + fn complex_inverse_fails_on_wrong_dims() { + let mut a_2x3 = ComplexMatrix::new(2, 3); + let mut a_2x2 = ComplexMatrix::new(2, 2); + let mut ai_1x2 = ComplexMatrix::new(1, 2); + let mut ai_2x1 = ComplexMatrix::new(2, 1); + assert_eq!( + complex_mat_inverse(&mut ai_1x2, &mut a_2x3), + Err("matrix must be square") + ); + assert_eq!( + complex_mat_inverse(&mut ai_1x2, &mut a_2x2), + Err("matrices are incompatible") + ); + assert_eq!( + complex_mat_inverse(&mut ai_2x1, &mut a_2x2), + Err("matrices are incompatible") + ); + } + + #[test] + fn complex_inverse_0x0_works() { + let mut a = ComplexMatrix::new(0, 0); + let mut ai = ComplexMatrix::new(0, 0); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + assert_eq!(det, cpx!(0.0, 0.0)); + assert_eq!(ai.as_data().len(), 0); + } + + #[test] + fn complex_inverse_1x1_works() { + let data = [[2.0]]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(1, 1); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + assert_eq!(det, cpx!(2.0, 0.0)); + complex_mat_approx_eq(&ai, &[[cpx!(0.5, 0.0)]], 1e-15); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &[[cpx!(1.0, 0.0)]], 1e-15); + } + + #[test] + fn complex_inverse_1x1_fails_on_zero_det() { + let mut a = ComplexMatrix::from(&[[ZERO_DETERMINANT_NORM / 10.0]]); + let mut ai = ComplexMatrix::new(1, 1); + let res = complex_mat_inverse(&mut ai, &mut a); + assert_eq!(res, Err("cannot compute inverse due to zero determinant")); + } + + #[test] + fn complex_inverse_2x2_works() { + #[rustfmt::skip] + let data = [ + [1.0, 2.0], + [3.0, 2.0], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(2, 2); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + assert_eq!(det, cpx!(-4.0, 0.0)); + complex_mat_approx_eq( + &ai, + &[[cpx!(-0.5, 0.0), cpx!(0.5, 0.0)], [cpx!(0.75, 0.0), cpx!(-0.25, 0.0)]], + 1e-15, + ); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq( + &a_ai, + &[[cpx!(1.0, 0.0), cpx!(0.0, 0.0)], [cpx!(0.0, 0.0), cpx!(1.0, 0.0)]], + 1e-15, + ); + } + + #[test] + fn complex_inverse_2x2_fails_on_zero_det() { + #[rustfmt::skip] + let mut a = ComplexMatrix::from(&[ + [ -1.0, 3.0/2.0], + [2.0/3.0, -1.0], + ]); + let mut ai = ComplexMatrix::new(2, 2); + let res = complex_mat_inverse(&mut ai, &mut a); + assert_eq!(res, Err("cannot compute inverse due to zero determinant")); + } + + #[test] + fn complex_inverse_3x3_works() { + #[rustfmt::skip] + let data = [ + [1.0, 2.0, 3.0], + [0.0, 4.0, 5.0], + [1.0, 0.0, 6.0], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(3, 3); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + assert_eq!(det, cpx!(22.0, 0.0)); + #[rustfmt::skip] + let ai_correct = &[ + [cpx!(12.0/11.0, 0.0), cpx!(-6.0/11.0, 0.0), cpx!(-1.0/11.0, 0.0)], + [cpx!( 2.5/11.0, 0.0), cpx!( 1.5/11.0, 0.0), cpx!(-2.5/11.0, 0.0)], + [cpx!(-2.0/11.0, 0.0), cpx!( 1.0/11.0, 0.0), cpx!( 2.0/11.0, 0.0)], + ]; + complex_mat_approx_eq(&ai, ai_correct, 1e-15); + let identity = ComplexMatrix::identity(3); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &identity, 1e-15); + } + + #[test] + fn complex_inverse_3x3_works_imag() { + #[rustfmt::skip] + let data = [ + [cpx!( 2.0, 1.0), cpx!(-1.0, -1.0), cpx!( 0.0, 0.0)], + [cpx!(-1.0, -1.0), cpx!( 2.0, 2.0), cpx!(-1.0, 1.0)], + [cpx!( 0.0, 0.0), cpx!(-1.0, 1.0), cpx!( 2.0, -1.0)], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(3, 3); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + assert_eq!(det, cpx!(6.0, 10.0)); + #[rustfmt::skip] + let ai_correct = &[ + [cpx!(19.0/34.0, -9.0/34.0), cpx!( 7.0/34.0, -6.0/34.0), cpx!( 3.0/34.0, -5.0/34.0)], + [cpx!( 7.0/34.0, -6.0/34.0), cpx!(15.0/68.0, -25.0/68.0), cpx!( 2.0/34.0, -9.0/34.0)], + [cpx!( 3.0/34.0, -5.0/34.0), cpx!( 2.0/34.0, -9.0/34.0), cpx!(13.0/34.0, 1.0/34.0)], + ]; + complex_mat_approx_eq(&ai, ai_correct, 1e-15); + let identity = ComplexMatrix::identity(3); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &identity, 1e-15); + } + + #[test] + fn complex_inverse_3x3_fails_on_zero_det() { + #[rustfmt::skip] + let mut a = ComplexMatrix::from(&[ + [1.0, 0.0, 3.0], + [0.0, 0.0, 5.0], + [1.0, 0.0, 6.0], + ]); + let mut ai = ComplexMatrix::new(3, 3); + let res = complex_mat_inverse(&mut ai, &mut a); + assert_eq!(res, Err("cannot compute inverse due to zero determinant")); + } + + #[test] + fn complex_inverse_4x4_works() { + #[rustfmt::skip] + let data = [ + [ 3.0, 0.0, 2.0, -1.0], + [ 1.0, 2.0, 0.0, -2.0], + [ 4.0, 0.0, 6.0, -3.0], + [ 5.0, 0.0, 2.0, 0.0], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(4, 4); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + complex_approx_eq(det, cpx!(20.0, 0.0), 1e-14); + #[rustfmt::skip] + let ai_correct = &[ + [cpx!( 0.6, 0.0), cpx!(0.0, 0.0), cpx!(-0.2, 0.0), cpx!(0.0, 0.0)], + [cpx!(-2.5, 0.0), cpx!(0.5, 0.0), cpx!( 0.5, 0.0), cpx!(1.0, 0.0)], + [cpx!(-1.5, 0.0), cpx!(0.0, 0.0), cpx!( 0.5, 0.0), cpx!(0.5, 0.0)], + [cpx!(-2.2, 0.0), cpx!(0.0, 0.0), cpx!( 0.4, 0.0), cpx!(1.0, 0.0)], + ]; + complex_mat_approx_eq(&ai, ai_correct, 1e-15); + let identity = ComplexMatrix::identity(4); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &identity, 1e-15); + } + + #[test] + fn complex_inverse_4x4_works_imag() { + #[rustfmt::skip] + let data = [ + [cpx!(1.0, 1.0), cpx!(2.0, 0.0), cpx!( 0.0, 0.0), cpx!(1.0, -1.0)], + [cpx!(2.0, 1.0), cpx!(3.0, 0.0), cpx!(-1.0, 0.0), cpx!(1.0, -1.0)], + [cpx!(1.0, 1.0), cpx!(2.0, 0.0), cpx!( 0.0, 0.0), cpx!(4.0, -1.0)], + [cpx!(4.0, 1.0), cpx!(0.0, 0.0), cpx!( 3.0, 0.0), cpx!(1.0, -1.0)], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(4, 4); + complex_mat_inverse(&mut ai, &mut a).unwrap(); + #[rustfmt::skip] + let ai_correct = &[ + [cpx!(-8.442622950819669e-01, -4.644808743169393e-02), cpx!( 5.409836065573769e-01, 4.918032786885240e-02), cpx!( 3.278688524590156e-02, -2.732240437158467e-02), cpx!( 1.803278688524591e-01, 1.639344262295081e-02)], + [cpx!( 1.065573770491803e+00, 2.786885245901638e-01), cpx!(-2.459016393442623e-01, -2.950819672131146e-01), cpx!(-1.967213114754096e-01, 1.639344262295082e-01), cpx!(-8.196721311475419e-02, -9.836065573770497e-02)], + [cpx!( 1.221311475409836e+00, 2.322404371584698e-01), cpx!(-7.049180327868851e-01, -2.459016393442622e-01), cpx!(-1.639344262295082e-01, 1.366120218579235e-01), cpx!( 9.836065573770481e-02, -8.196721311475411e-02)], + [cpx!(-3.333333333333333e-01, 0.000000000000000e+00), cpx!( 0.000000000000000e+00, 0.000000000000000e+00), cpx!( 3.333333333333333e-01, 0.000000000000000e+00), cpx!( 0.000000000000000e+00, 0.000000000000000e+00)], + ]; + complex_mat_approx_eq(&ai, ai_correct, 1e-15); + let identity = ComplexMatrix::identity(4); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &identity, 1e-15); + } + + #[test] + fn complex_inverse_5x5_works() { + #[rustfmt::skip] + let data = [ + [cpx!(12.0,0.0), cpx!(28.0,0.0), cpx!(22.0,0.0), cpx!(20.0,0.0), cpx!( 8.0,0.0)], + [cpx!( 0.0,0.0), cpx!( 3.0,0.0), cpx!( 5.0,0.0), cpx!(17.0,0.0), cpx!(28.0,0.0)], + [cpx!(56.0,0.0), cpx!( 0.0,0.0), cpx!(23.0,0.0), cpx!( 1.0,0.0), cpx!( 0.0,0.0)], + [cpx!(12.0,0.0), cpx!(29.0,0.0), cpx!(27.0,0.0), cpx!(10.0,0.0), cpx!( 1.0,0.0)], + [cpx!( 9.0,0.0), cpx!( 4.0,0.0), cpx!(13.0,0.0), cpx!( 8.0,0.0), cpx!(22.0,0.0)], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(5, 5); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + complex_approx_eq(det, cpx!(-167402.0, 0.0), 1e-8); + #[rustfmt::skip] + let ai_correct = &[ + [cpx!( 6.9128803717996279e-01,0.0), cpx!(-7.4226114383340802e-01,0.0), cpx!(-9.8756287260606410e-02,0.0), cpx!(-6.9062496266472417e-01,0.0), cpx!( 7.2471057693456553e-01,0.0)], + [cpx!( 1.5936129795342968e+00,0.0), cpx!(-1.7482347881148397e+00,0.0), cpx!(-2.8304321334273236e-01,0.0), cpx!(-1.5600769405383470e+00,0.0), cpx!( 1.7164430532490673e+00,0.0)], + [cpx!(-1.6345384165063759e+00,0.0), cpx!( 1.7495848317224429e+00,0.0), cpx!( 2.7469205863729274e-01,0.0), cpx!( 1.6325730875377857e+00,0.0), cpx!(-1.7065745928961444e+00,0.0)], + [cpx!(-1.1177465024312745e+00,0.0), cpx!( 1.3261729250546601e+00,0.0), cpx!( 2.1243473793622566e-01,0.0), cpx!( 1.1258168958554866e+00,0.0), cpx!(-1.3325766717243535e+00,0.0)], + [cpx!( 7.9976941733073770e-01,0.0), cpx!(-8.9457712572131853e-01,0.0), cpx!(-1.4770432850264653e-01,0.0), cpx!(-8.0791149448632715e-01,0.0), cpx!( 9.2990525800169743e-01,0.0)], + ]; + complex_mat_approx_eq(&ai, ai_correct, 1e-13); + let identity = ComplexMatrix::identity(5); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &identity, 1e-13); + } + + #[test] + fn complex_inverse_6x6_works() { + // NOTE: this matrix is nearly non-invertible; it originated from an FEM analysis + #[rustfmt::skip] + let data = [ + [cpx!( 3.46540497998689445e-05,0.0), cpx!(-1.39368151175265866e-05,0.0), cpx!(-1.39368151175265866e-05,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(7.15957288480514429e-23,0.0), cpx!(-2.93617909908697186e+02,0.0)], + [cpx!(-1.39368151175265866e-05,0.0), cpx!( 3.46540497998689445e-05,0.0), cpx!(-1.39368151175265866e-05,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(7.15957288480514429e-23,0.0), cpx!(-2.93617909908697186e+02,0.0)], + [cpx!(-1.39368151175265866e-05,0.0), cpx!(-1.39368151175265866e-05,0.0), cpx!( 3.46540497998689445e-05,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(7.15957288480514429e-23,0.0), cpx!(-2.93617909908697186e+02,0.0)], + [cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!( 4.85908649173955311e-05,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0)], + [cpx!( 3.13760264822604860e-18,0.0), cpx!( 3.13760264822604860e-18,0.0), cpx!( 3.13760264822604860e-18,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(1.00000000000000000e+00,0.0), cpx!(-1.93012141894243434e+07,0.0)], + [cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(-0.00000000000000000e+00,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!( 1.00000000000000000e+00,0.0)], + ]; + let mut a = ComplexMatrix::from(&data); + let mut ai = ComplexMatrix::new(6, 6); + let det = complex_mat_inverse(&mut ai, &mut a).unwrap(); + complex_approx_eq(det, cpx!(7.778940633136385e-19, 0.0), 1e-15); + #[rustfmt::skip] + let ai_correct = &[ + [cpx!( 6.28811662297464645e+04,0.0), cpx!( 4.23011662297464645e+04,0.0), cpx!( 4.23011662297464645e+04,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!(-1.05591885817167332e-17,0.0), cpx!(4.33037966311565489e+07,0.0)], + [cpx!( 4.23011662297464645e+04,0.0), cpx!( 6.28811662297464645e+04,0.0), cpx!( 4.23011662297464645e+04,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!(-1.05591885817167332e-17,0.0), cpx!(4.33037966311565489e+07,0.0)], + [cpx!( 4.23011662297464645e+04,0.0), cpx!( 4.23011662297464645e+04,0.0), cpx!( 6.28811662297464645e+04,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!(-1.05591885817167348e-17,0.0), cpx!(4.33037966311565489e+07,0.0)], + [cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(2.05800000000000000e+04,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(0.00000000000000000e+00,0.0)], + [cpx!(-4.62744616057000471e-13,0.0), cpx!(-4.62744616057000471e-13,0.0), cpx!(-4.62744616057000471e-13,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!( 1.00000000000000000e+00,0.0), cpx!(1.93012141894243434e+07,0.0)], + [cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(0.00000000000000000e+00,0.0), cpx!( 0.00000000000000000e+00,0.0), cpx!(1.00000000000000000e+00,0.0)], + ]; + complex_mat_approx_eq(&ai, ai_correct, 1e-8); + let identity = ComplexMatrix::identity(6); + let a_copy = ComplexMatrix::from(&data); + let a_ai = get_a_times_ai(&a_copy, &ai); + complex_mat_approx_eq(&a_ai, &identity, 1e-12); + } +} diff --git a/russell_lab/src/matrix/complex_mat_mat_mul.rs b/russell_lab/src/matrix/complex_mat_mat_mul.rs index 510e0f98..fcacd602 100644 --- a/russell_lab/src/matrix/complex_mat_mat_mul.rs +++ b/russell_lab/src/matrix/complex_mat_mat_mul.rs @@ -4,7 +4,7 @@ use num_complex::Complex64; extern "C" { // Performs the matrix-matrix multiplication (complex version) - // + // fn cblas_zgemm( layout: i32, transa: i32, diff --git a/russell_lab/src/matrix/complex_mat_norm.rs b/russell_lab/src/matrix/complex_mat_norm.rs index 753c80d3..dfad5908 100644 --- a/russell_lab/src/matrix/complex_mat_norm.rs +++ b/russell_lab/src/matrix/complex_mat_norm.rs @@ -4,7 +4,7 @@ use num_complex::Complex64; extern "C" { // Computes the matrix norm (complex version) - // + // fn c_zlange( norm_code: i32, m: *const i32, diff --git a/russell_lab/src/matrix/complex_mat_unzip.rs b/russell_lab/src/matrix/complex_mat_unzip.rs new file mode 100644 index 00000000..eb651ae8 --- /dev/null +++ b/russell_lab/src/matrix/complex_mat_unzip.rs @@ -0,0 +1,98 @@ +use crate::ComplexMatrix; +use crate::Matrix; +use crate::StrError; + +/// Zips two arrays (real and imag) to make a new ComplexMatrix +/// +/// # Example +/// +/// ``` +/// use russell_lab::*; +/// use num_complex::Complex64; +/// +/// fn main() -> Result<(), StrError> { +/// let a = ComplexMatrix::from(&[ +/// [cpx!(1.0, 0.1), cpx!(2.0, 0.2), cpx!(3.0, 0.3)], +/// [cpx!(4.0, 0.4), cpx!(5.0, 0.5), cpx!(6.0, 0.6)], +/// ]); +/// let mut real = Matrix::new(2, 3); +/// let mut imag = Matrix::new(2, 3); +/// complex_mat_unzip(&mut real, &mut imag, &a)?; +/// assert_eq!( +/// format!("{}", real), +/// "┌ ┐\n\ +/// │ 1 2 3 │\n\ +/// │ 4 5 6 │\n\ +/// └ ┘" +/// ); +/// assert_eq!( +/// format!("{}", imag), +/// "┌ ┐\n\ +/// │ 0.1 0.2 0.3 │\n\ +/// │ 0.4 0.5 0.6 │\n\ +/// └ ┘" +/// ); +/// Ok(()) +/// } +/// ``` +pub fn complex_mat_unzip(real: &mut Matrix, imag: &mut Matrix, a: &ComplexMatrix) -> Result<(), StrError> { + let (nrow, ncol) = a.dims(); + let (nrow_re, ncol_re) = real.dims(); + let (nrow_im, ncol_im) = imag.dims(); + if nrow_re != nrow || ncol_re != ncol || nrow_im != nrow || ncol_im != ncol { + return Err("matrices are incompatible"); + } + for i in 0..nrow { + for j in 0..ncol { + real.set(i, j, a.get(i, j).re); + imag.set(i, j, a.get(i, j).im); + } + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::complex_mat_unzip; + use crate::{cpx, mat_approx_eq, ComplexMatrix, Matrix}; + use num_complex::Complex64; + + #[test] + fn complex_mat_unzip_handles_errors() { + let a = ComplexMatrix::new(2, 2); + let mut wrong_1x2 = Matrix::new(1, 2); + let mut wrong_2x1 = Matrix::new(2, 1); + let mut ok = Matrix::new(2, 2); + assert_eq!( + complex_mat_unzip(&mut wrong_1x2, &mut ok, &a).err(), + Some("matrices are incompatible") + ); + assert_eq!( + complex_mat_unzip(&mut wrong_2x1, &mut ok, &a).err(), + Some("matrices are incompatible") + ); + assert_eq!( + complex_mat_unzip(&mut ok, &mut wrong_1x2, &a).err(), + Some("matrices are incompatible") + ); + assert_eq!( + complex_mat_unzip(&mut ok, &mut wrong_2x1, &a).err(), + Some("matrices are incompatible") + ); + } + + #[test] + fn complex_mat_unzip_works() { + let a = ComplexMatrix::from(&[ + [cpx!(1.0, 0.1), cpx!(2.0, 0.2), cpx!(3.0, 0.3)], + [cpx!(4.0, 0.4), cpx!(5.0, 0.5), cpx!(6.0, 0.6)], + ]); + let mut real = Matrix::new(2, 3); + let mut imag = Matrix::new(2, 3); + complex_mat_unzip(&mut real, &mut imag, &a).unwrap(); + mat_approx_eq(&real, &[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], 1e-15); + mat_approx_eq(&imag, &[[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], 1e-15); + } +} diff --git a/russell_lab/src/matrix/complex_mat_update.rs b/russell_lab/src/matrix/complex_mat_update.rs new file mode 100644 index 00000000..dc0d8736 --- /dev/null +++ b/russell_lab/src/matrix/complex_mat_update.rs @@ -0,0 +1,127 @@ +use super::ComplexMatrix; +use crate::{to_i32, StrError}; +use num_complex::Complex64; + +extern "C" { + // Computes constant times a vector plus a vector (Complex version) + // + fn cblas_zaxpy(n: i32, alpha: *const Complex64, x: *const Complex64, incx: i32, y: *mut Complex64, incy: i32); +} + +/// Updates matrix based on another matrix (Complex version) +/// +/// ```text +/// b += α⋅a +/// ``` +/// +/// # Example +/// +/// ``` +/// use russell_lab::{cpx, complex_mat_update, ComplexMatrix, StrError}; +/// use num_complex::Complex64; +/// +/// fn main() -> Result<(), StrError> { +/// let a = ComplexMatrix::from(&[ +/// [10.0, 20.0, 30.0], +/// [40.0, 50.0, 60.0], +/// ]); +/// let mut b = ComplexMatrix::from(&[ +/// [10.0, 20.0, 30.0], +/// [40.0, 50.0, 60.0], +/// ]); +/// complex_mat_update(&mut b, cpx!(0.1, 0.0), &a)?; +/// let correct = "┌ ┐\n\ +/// │ 11+0i 22+0i 33+0i │\n\ +/// │ 44+0i 55+0i 66+0i │\n\ +/// └ ┘"; +/// assert_eq!(format!("{}", b), correct); +/// Ok(()) +/// } +/// ``` +pub fn complex_mat_update(b: &mut ComplexMatrix, alpha: Complex64, a: &ComplexMatrix) -> Result<(), StrError> { + let (m, n) = b.dims(); + if a.nrow() != m || a.ncol() != n { + return Err("matrices are incompatible"); + } + let mn_i32 = to_i32(m * n); + unsafe { + cblas_zaxpy(mn_i32, &alpha, a.as_data().as_ptr(), 1, b.as_mut_data().as_mut_ptr(), 1); + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::complex_mat_update; + use crate::{complex_mat_approx_eq, cpx, ComplexMatrix}; + use num_complex::Complex64; + + #[test] + fn complex_mat_update_fail_on_wrong_dims() { + let a_2x2 = ComplexMatrix::new(2, 2); + let a_2x1 = ComplexMatrix::new(2, 1); + let a_1x2 = ComplexMatrix::new(1, 2); + let mut b_2x2 = ComplexMatrix::new(2, 2); + let mut b_2x1 = ComplexMatrix::new(2, 1); + let mut b_1x2 = ComplexMatrix::new(1, 2); + assert_eq!( + complex_mat_update(&mut b_2x2, cpx!(2.0, 0.0), &a_2x1), + Err("matrices are incompatible") + ); + assert_eq!( + complex_mat_update(&mut b_2x2, cpx!(2.0, 0.0), &a_1x2), + Err("matrices are incompatible") + ); + assert_eq!( + complex_mat_update(&mut b_2x1, cpx!(2.0, 0.0), &a_2x2), + Err("matrices are incompatible") + ); + assert_eq!( + complex_mat_update(&mut b_1x2, cpx!(2.0, 0.0), &a_2x2), + Err("matrices are incompatible") + ); + } + + #[test] + fn complex_mat_update_works() { + // real only + #[rustfmt::skip] + let a = ComplexMatrix::from(&[ + [cpx!(10.0, 0.0), cpx!(20.0, 0.0), cpx!(30.0, 0.0)], + [cpx!(40.0, 0.0), cpx!(50.0, 0.0), cpx!(60.0, 0.0)], + ]); + #[rustfmt::skip] + let mut b = ComplexMatrix::from(&[ + [cpx!(100.0, 0.0), cpx!(200.0, 0.0), cpx!(300.0, 0.0)], + [cpx!(400.0, 0.0), cpx!(500.0, 0.0), cpx!(600.0, 0.0)], + ]); + complex_mat_update(&mut b, cpx!(2.0, 0.0), &a).unwrap(); + #[rustfmt::skip] + let correct = &[ + [cpx!(120.0, 0.0), cpx!(240.0, 0.0), cpx!(360.0, 0.0)], + [cpx!(480.0, 0.0), cpx!(600.0, 0.0), cpx!(720.0, 0.0)], + ]; + complex_mat_approx_eq(&b, correct, 1e-15); + + // real and imag + #[rustfmt::skip] + let a = ComplexMatrix::from(&[ + [cpx!(10.0, 1.0), cpx!(20.0, 2.0), cpx!(30.0, 3.0)], + [cpx!(40.0, 4.0), cpx!(50.0, 5.0), cpx!(60.0, 6.0)], + ]); + #[rustfmt::skip] + let mut b = ComplexMatrix::from(&[ + [cpx!(100.0, -1.0), cpx!(200.0, 1.0), cpx!(300.0, -1.0)], + [cpx!(400.0, -1.0), cpx!(500.0, 1.0), cpx!(600.0, -1.0)], + ]); + complex_mat_update(&mut b, cpx!(2.0, 1.0), &a).unwrap(); + #[rustfmt::skip] + let correct = &[ + [cpx!(119.0, 11.0), cpx!(238.0, 25.0), cpx!(357.0, 35.0)], + [cpx!(476.0, 47.0), cpx!(595.0, 61.0), cpx!(714.0, 71.0)], + ]; + complex_mat_approx_eq(&b, correct, 1e-15); + } +} diff --git a/russell_lab/src/matrix/complex_mat_zip.rs b/russell_lab/src/matrix/complex_mat_zip.rs index e5fe0d95..dde3ffd0 100644 --- a/russell_lab/src/matrix/complex_mat_zip.rs +++ b/russell_lab/src/matrix/complex_mat_zip.rs @@ -8,14 +8,15 @@ use num_complex::Complex64; /// # Example /// /// ``` -/// use russell_lab::{complex_mat_zip, Matrix, StrError}; +/// use russell_lab::*; /// /// fn main() -> Result<(), StrError> { -/// let a = Matrix::from(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]); -/// let b = Matrix::from(&[[0.1, 0.2, 0.3], [-0.4, -0.5, -0.6]]); -/// let c = complex_mat_zip(&a, &b)?; +/// let mut a = ComplexMatrix::new(2, 3); +/// let real = Matrix::from(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]); +/// let imag = Matrix::from(&[[0.1, 0.2, 0.3], [-0.4, -0.5, -0.6]]); +/// complex_mat_zip(&mut a, &real, &imag)?; /// assert_eq!( -/// format!("{}", c), +/// format!("{}", a), /// "┌ ┐\n\ /// │ 1+0.1i 2+0.2i 3+0.3i │\n\ /// │ 4-0.4i 5-0.5i 6-0.6i │\n\ @@ -24,19 +25,19 @@ use num_complex::Complex64; /// Ok(()) /// } /// ``` -pub fn complex_mat_zip(real: &Matrix, imag: &Matrix) -> Result { - let (m, n) = real.dims(); - let (mm, nn) = imag.dims(); - if mm != m || nn != n { +pub fn complex_mat_zip(a: &mut ComplexMatrix, real: &Matrix, imag: &Matrix) -> Result<(), StrError> { + let (nrow, ncol) = a.dims(); + let (nrow_re, ncol_re) = real.dims(); + let (nrow_im, ncol_im) = imag.dims(); + if nrow_re != nrow || ncol_re != ncol || nrow_im != nrow || ncol_im != ncol { return Err("matrices are incompatible"); } - let mut a = ComplexMatrix::new(m, n); - for i in 0..m { - for j in 0..n { + for i in 0..nrow { + for j in 0..ncol { a.set(i, j, Complex64::new(real.get(i, j), imag.get(i, j))); } } - Ok(a) + Ok(()) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -44,20 +45,38 @@ pub fn complex_mat_zip(real: &Matrix, imag: &Matrix) -> Result + // fn c_dpotrf(upper: CcBool, n: *const i32, a: *mut f64, lda: *const i32, info: *mut i32); } diff --git a/russell_lab/src/matrix/mat_convert_to_blas_band.rs b/russell_lab/src/matrix/mat_convert_to_blas_band.rs new file mode 100644 index 00000000..e2ba140d --- /dev/null +++ b/russell_lab/src/matrix/mat_convert_to_blas_band.rs @@ -0,0 +1,157 @@ +use super::Matrix; +use crate::StrError; + +/// Converts a general (dense) matrix to the BLAS band matrix format +/// +/// Only the band elements of a general banded matrix are stored in the BLAS band format. +/// The storage mode packs the matrix elements by columns such that each diagonal of the +/// matrix appears as a row in the packed array. +/// +/// # Output +/// +/// * `band` -- the BLAS band storage matrix. It must be an `(ml + mu + 1, n)` matrix +/// +/// **Warning:** The output matrix `band` is not zeroed; thus, any existing value at the "out of band" +/// positions will be preserved. +/// +/// # Input +/// +/// * `dense` -- the general `(m, n)` matrix +/// * `ml` -- the lower diagonal dimension (does not count the diagonal) +/// * `mu` -- the upper diagonal dimension (does not count the diagonal) +/// +/// See example below. +/// +/// ```text +/// Input: +/// =3 +/// ↓---mu--↓ +/// ┌ ┐ +/// | 11 12 13 14 · · · · | +/// → │ 21 22 23 24 25 · · · | +/// ml = 2 → │ 31 32 33 34 35 36 · · | +/// │ · 42 43 44 45 46 47 · | +/// dense = │ · · 53 54 55 56 57 58 | +/// │ · · · 64 65 66 67 68 | +/// │ · · · · 75 76 77 78 | +/// │ · · · · · 86 87 88 | +/// │ · · · · · · 97 98 | +/// └ ┘ +/// ``` +/// +/// Imagine that you "push" the left-hand side down to make a "more rectangle" array, resulting in: +/// +/// ```text +/// Output: +/// ┌ ┐ +/// │ · · · 14 25 36 47 58 | +/// │ · · 13 24 35 46 57 68 | +/// band = │ · 12 23 34 45 56 67 78 | +/// │ 11 22 33 44 55 66 77 88 | +/// │ 21 32 43 54 65 76 87 98 | +/// │ 31 42 53 64 75 86 97 · | +/// └ ┘ +/// ``` +/// +/// # References +/// +/// * +/// +pub fn mat_convert_to_blas_band(band: &mut Matrix, dense: &Matrix, ml: usize, mu: usize) -> Result<(), StrError> { + let (m, n) = dense.dims(); + let (mb, nb) = band.dims(); + if mb != ml + mu + 1 || nb != n { + return Err("the resulting matrix must be ml + mu + 1 by n"); + } + for j in 0..n { + let a = if j > mu { j - mu } else { 0 }; + let b = if j + ml + 1 < m { j + ml + 1 } else { m }; + for i in a..b { + band.set(i + mu - j, j, dense.get(i, j)); + } + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::mat_convert_to_blas_band; + use crate::Matrix; + + #[test] + fn mat_convert_to_blas_band_captures_errors() { + let dense = Matrix::from(&[[1.0, 2.0], [3.0, 4.0]]); + let n = dense.dims().1; + let (ml, mu) = (1, 1); + let mut band_wrong = Matrix::new(ml + mu + 0, n); + assert_eq!( + mat_convert_to_blas_band(&mut band_wrong, &dense, ml, mu).err(), + Some("the resulting matrix must be ml + mu + 1 by n") + ); + let mut band_wrong = Matrix::new(ml + mu + 0, n + 1); + assert_eq!( + mat_convert_to_blas_band(&mut band_wrong, &dense, ml, mu).err(), + Some("the resulting matrix must be ml + mu + 1 by n") + ); + } + + #[test] + fn mat_convert_to_blas_band_works_m_gt_n() { + #[rustfmt::skip] + let dense = Matrix::from(&[ + [11.0, 12.0, 13.0, 14.0, 0.0, 0.0, 0.0, 0.0], + [21.0, 22.0, 23.0, 24.0, 25.0, 0.0, 0.0, 0.0], + [31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 0.0, 0.0], + [ 0.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 0.0], + [ 0.0, 0.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0], + [ 0.0, 0.0, 0.0, 64.0, 65.0, 66.0, 67.0, 68.0], + [ 0.0, 0.0, 0.0, 0.0, 75.0, 76.0, 77.0, 78.0], + [ 0.0, 0.0, 0.0, 0.0, 0.0, 86.0, 87.0, 88.0], + [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 97.0, 98.0], + ]); + #[rustfmt::skip] + let band_correct = Matrix::from(&[ + [ 0.0, 0.0, 0.0, 14.0, 25.0, 36.0, 47.0, 58.0], + [ 0.0, 0.0, 13.0, 24.0, 35.0, 46.0, 57.0, 68.0], + [ 0.0, 12.0, 23.0, 34.0, 45.0, 56.0, 67.0, 78.0], + [11.0, 22.0, 33.0, 44.0, 55.0, 66.0, 77.0, 88.0], + [21.0, 32.0, 43.0, 54.0, 65.0, 76.0, 87.0, 98.0], + [31.0, 42.0, 53.0, 64.0, 75.0, 86.0, 97.0, 0.0], + ]); + let n = dense.dims().1; + let (ml, mu) = (2, 3); + let mut band = Matrix::new(ml + mu + 1, n); + mat_convert_to_blas_band(&mut band, &dense, ml, mu).unwrap(); + assert_eq!(band.as_data(), band_correct.as_data()); + } + + #[test] + fn mat_convert_to_blas_band_works_n_gt_m() { + #[rustfmt::skip] + let dense = Matrix::from(&[ + [11.0, 12.0, 13.0, 14.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [21.0, 22.0, 23.0, 24.0, 25.0, 0.0, 0.0, 0.0, 0.0], + [31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 0.0, 0.0, 0.0], + [ 0.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 0.0, 0.0], + [ 0.0, 0.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 0.0], + [ 0.0, 0.0, 0.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0], + [ 0.0, 0.0, 0.0, 0.0, 75.0, 76.0, 77.0, 78.0, 79.0], + ]); + #[rustfmt::skip] + let band_correct = Matrix::from(&[ + [ 0.0, 0.0, 0.0, 14.0, 25.0, 36.0, 47.0, 58.0, 69.0], + [ 0.0, 0.0, 13.0, 24.0, 35.0, 46.0, 57.0, 68.0, 79.0], + [ 0.0, 12.0, 23.0, 34.0, 45.0, 56.0, 67.0, 78.0, 0.0], + [11.0, 22.0, 33.0, 44.0, 55.0, 66.0, 77.0, 0.0, 0.0], + [21.0, 32.0, 43.0, 54.0, 65.0, 76.0, 0.0, 0.0, 0.0], + [31.0, 42.0, 53.0, 64.0, 75.0, 0.0, 0.0, 0.0, 0.0], + ]); + let n = dense.dims().1; + let (ml, mu) = (2, 3); + let mut band = Matrix::new(ml + mu + 1, n); + mat_convert_to_blas_band(&mut band, &dense, ml, mu).unwrap(); + assert_eq!(band.as_data(), band_correct.as_data()); + } +} diff --git a/russell_lab/src/matrix/mat_eigen.rs b/russell_lab/src/matrix/mat_eigen.rs index 5a0664e9..9fab5d7c 100644 --- a/russell_lab/src/matrix/mat_eigen.rs +++ b/russell_lab/src/matrix/mat_eigen.rs @@ -3,7 +3,7 @@ use crate::{dgeev_data, dgeev_data_lr, to_i32, CcBool, StrError, Vector, C_FALSE extern "C" { // Computes the eigenvalues and eigenvectors of a general matrix - // + // fn c_dgeev( calc_vl: CcBool, calc_vr: CcBool, @@ -266,8 +266,10 @@ pub fn mat_eigen( /// // err := a⋅v - v⋅λ /// // ``` /// let a = ComplexMatrix::from(&data); -/// let v = complex_mat_zip(&v_real, &v_imag)?; -/// let d = complex_vec_zip(&l_real, &l_imag)?; +/// let mut v = ComplexMatrix::new(m, m); +/// let mut d = ComplexVector::new(m); +/// complex_mat_zip(&mut v, &v_real, &v_imag)?; +/// complex_vec_zip(&mut d, &l_real, &l_imag)?; /// let lam = ComplexMatrix::diagonal(d.as_data()); /// let mut a_v = ComplexMatrix::new(m, m); /// let mut v_l = ComplexMatrix::new(m, m); diff --git a/russell_lab/src/matrix/mat_inverse.rs b/russell_lab/src/matrix/mat_inverse.rs index 2e3ac541..f2670c49 100644 --- a/russell_lab/src/matrix/mat_inverse.rs +++ b/russell_lab/src/matrix/mat_inverse.rs @@ -3,11 +3,11 @@ use crate::{to_i32, StrError}; extern "C" { // Computes the LU factorization of a general (m,n) matrix - /// + /// fn c_dgetrf(m: *const i32, n: *const i32, a: *mut f64, lda: *const i32, ipiv: *mut i32, info: *mut i32); // Computes the inverse of a matrix using the LU factorization computed by dgetrf - /// + /// fn c_dgetri( n: *const i32, a: *mut f64, diff --git a/russell_lab/src/matrix/mat_mat_mul.rs b/russell_lab/src/matrix/mat_mat_mul.rs index 793d8d22..9bf07c99 100644 --- a/russell_lab/src/matrix/mat_mat_mul.rs +++ b/russell_lab/src/matrix/mat_mat_mul.rs @@ -3,7 +3,7 @@ use crate::{to_i32, StrError, CBLAS_COL_MAJOR, CBLAS_NO_TRANS}; extern "C" { // Performs the matrix-matrix multiplication - // + // fn cblas_dgemm( layout: i32, transa: i32, diff --git a/russell_lab/src/matrix/mat_norm.rs b/russell_lab/src/matrix/mat_norm.rs index ffeefd93..d63e45cc 100644 --- a/russell_lab/src/matrix/mat_norm.rs +++ b/russell_lab/src/matrix/mat_norm.rs @@ -3,7 +3,7 @@ use crate::{to_i32, Norm}; extern "C" { // Computes the matrix norm - // + // fn c_dlange(norm_code: i32, m: *const i32, n: *const i32, a: *const f64, lda: *const i32, work: *mut f64) -> f64; } diff --git a/russell_lab/src/matrix/mat_scale.rs b/russell_lab/src/matrix/mat_scale.rs index a9bdd58e..eab21acb 100644 --- a/russell_lab/src/matrix/mat_scale.rs +++ b/russell_lab/src/matrix/mat_scale.rs @@ -3,7 +3,7 @@ use crate::to_i32; extern "C" { // Scales a vector by a constant - // + // fn cblas_dscal(n: i32, alpha: f64, x: *const f64, incx: i32); } diff --git a/russell_lab/src/matrix/mat_svd.rs b/russell_lab/src/matrix/mat_svd.rs index 4ffcf873..068a811e 100644 --- a/russell_lab/src/matrix/mat_svd.rs +++ b/russell_lab/src/matrix/mat_svd.rs @@ -4,7 +4,7 @@ use crate::{to_i32, StrError, SVD_CODE_A}; extern "C" { // Computes the singular value decomposition (SVD) - // + // fn c_dgesvd( jobu_code: i32, jobvt_code: i32, diff --git a/russell_lab/src/matrix/mat_t_mat_mul.rs b/russell_lab/src/matrix/mat_t_mat_mul.rs index b92ae773..68fcc9a0 100644 --- a/russell_lab/src/matrix/mat_t_mat_mul.rs +++ b/russell_lab/src/matrix/mat_t_mat_mul.rs @@ -3,7 +3,7 @@ use crate::{to_i32, StrError, CBLAS_COL_MAJOR, CBLAS_NO_TRANS, CBLAS_TRANS}; extern "C" { // Performs the matrix-matrix multiplication - // + // fn cblas_dgemm( layout: i32, transa: i32, diff --git a/russell_lab/src/matrix/mat_update.rs b/russell_lab/src/matrix/mat_update.rs index 7dfd083b..97a8f29d 100644 --- a/russell_lab/src/matrix/mat_update.rs +++ b/russell_lab/src/matrix/mat_update.rs @@ -3,7 +3,7 @@ use crate::{to_i32, StrError}; extern "C" { // Computes constant times a vector plus a vector. - // + // fn cblas_daxpy(n: i32, alpha: f64, x: *const f64, incx: i32, y: *mut f64, incy: i32); } diff --git a/russell_lab/src/matrix/mod.rs b/russell_lab/src/matrix/mod.rs index 808a7944..a257289f 100644 --- a/russell_lab/src/matrix/mod.rs +++ b/russell_lab/src/matrix/mod.rs @@ -3,12 +3,17 @@ mod aliases; mod complex_mat_add; mod complex_mat_approx_eq; +mod complex_mat_copy; +mod complex_mat_inverse; mod complex_mat_mat_mul; mod complex_mat_norm; +mod complex_mat_unzip; +mod complex_mat_update; mod complex_mat_zip; mod mat_add; mod mat_approx_eq; mod mat_cholesky; +mod mat_convert_to_blas_band; mod mat_copy; mod mat_eigen; mod mat_eigen_sym; @@ -28,12 +33,17 @@ mod testing; pub use crate::matrix::aliases::*; pub use crate::matrix::complex_mat_add::*; pub use crate::matrix::complex_mat_approx_eq::*; +pub use crate::matrix::complex_mat_copy::*; +pub use crate::matrix::complex_mat_inverse::*; pub use crate::matrix::complex_mat_mat_mul::*; pub use crate::matrix::complex_mat_norm::*; +pub use crate::matrix::complex_mat_unzip::*; +pub use crate::matrix::complex_mat_update::*; pub use crate::matrix::complex_mat_zip::*; pub use crate::matrix::mat_add::*; pub use crate::matrix::mat_approx_eq::*; pub use crate::matrix::mat_cholesky::*; +pub use crate::matrix::mat_convert_to_blas_band::*; pub use crate::matrix::mat_copy::*; pub use crate::matrix::mat_eigen::*; pub use crate::matrix::mat_eigen_sym::*; diff --git a/russell_lab/src/matrix/num_matrix.rs b/russell_lab/src/matrix/num_matrix.rs index 4e67fb83..94c79ac4 100644 --- a/russell_lab/src/matrix/num_matrix.rs +++ b/russell_lab/src/matrix/num_matrix.rs @@ -960,7 +960,6 @@ where mod tests { use super::NumMatrix; use crate::AsArray2D; - use serde::{Deserialize, Serialize}; #[test] fn new_works() { @@ -1371,24 +1370,6 @@ mod tests { └ ┘" ); - // serialize - let mut serialized = Vec::new(); - let mut serializer = rmp_serde::Serializer::new(&mut serialized); - a.serialize(&mut serializer).unwrap(); - assert!(serialized.len() > 0); - - // deserialize - let mut deserializer = rmp_serde::Deserializer::new(&serialized[..]); - let b: NumMatrix = Deserialize::deserialize(&mut deserializer).unwrap(); - assert_eq!( - format!("{}", b), - "┌ ┐\n\ - │ 1 2 3 │\n\ - │ 4 5 6 │\n\ - │ 7 8 9 │\n\ - └ ┘" - ); - // serialize to json let json = serde_json::to_string(&a).unwrap(); assert_eq!( diff --git a/russell_lab/src/matrix/testing.rs b/russell_lab/src/matrix/testing.rs index 64dcb631..b60664d7 100644 --- a/russell_lab/src/matrix/testing.rs +++ b/russell_lab/src/matrix/testing.rs @@ -1,6 +1,6 @@ use crate::{ approx_eq, complex_mat_add, complex_mat_mat_mul, complex_mat_norm, complex_mat_zip, complex_vec_zip, mat_add, - mat_mat_mul, mat_norm, AsArray2D, ComplexMatrix, Matrix, Norm, Vector, + mat_mat_mul, mat_norm, AsArray2D, ComplexMatrix, ComplexVector, Matrix, Norm, Vector, }; use num_complex::Complex64; @@ -52,8 +52,10 @@ pub(crate) fn check_eigen_general<'a, T>( { let a = ComplexMatrix::from(data); let m = a.nrow(); - let v = complex_mat_zip(v_real, v_imag).unwrap(); - let d = complex_vec_zip(l_real, l_imag).unwrap(); + let mut v = ComplexMatrix::new(m, m); + let mut d = ComplexVector::new(m); + complex_mat_zip(&mut v, v_real, v_imag).unwrap(); + complex_vec_zip(&mut d, l_real, l_imag).unwrap(); let lam = ComplexMatrix::diagonal(d.as_data()); let mut a_v = ComplexMatrix::new(m, m); let mut v_l = ComplexMatrix::new(m, m); diff --git a/russell_lab/src/matvec/complex_mat_vec_mul.rs b/russell_lab/src/matvec/complex_mat_vec_mul.rs index 7615f556..18c4fc37 100644 --- a/russell_lab/src/matvec/complex_mat_vec_mul.rs +++ b/russell_lab/src/matvec/complex_mat_vec_mul.rs @@ -5,7 +5,7 @@ use num_complex::Complex64; extern "C" { // Performs one of the matrix-vector multiplication (complex version) - // + // fn cblas_zgemv( layout: i32, transa: i32, diff --git a/russell_lab/src/matvec/complex_solve_lin_sys.rs b/russell_lab/src/matvec/complex_solve_lin_sys.rs index 8f89c927..94dcc02b 100644 --- a/russell_lab/src/matvec/complex_solve_lin_sys.rs +++ b/russell_lab/src/matvec/complex_solve_lin_sys.rs @@ -5,7 +5,7 @@ use num_complex::Complex64; extern "C" { // Computes the solution to a system of linear equations (complex version) - // + // fn c_zgesv( n: *const i32, nrhs: *const i32, diff --git a/russell_lab/src/matvec/complex_vec_mat_mul.rs b/russell_lab/src/matvec/complex_vec_mat_mul.rs index 526c07bd..9c387fe3 100644 --- a/russell_lab/src/matvec/complex_vec_mat_mul.rs +++ b/russell_lab/src/matvec/complex_vec_mat_mul.rs @@ -5,7 +5,7 @@ use num_complex::Complex64; extern "C" { // Performs one of the matrix-vector multiplication (complex version) - // + // fn cblas_zgemv( layout: i32, transa: i32, diff --git a/russell_lab/src/matvec/mat_vec_mul.rs b/russell_lab/src/matvec/mat_vec_mul.rs index 28db5bd9..2a92ffbb 100644 --- a/russell_lab/src/matvec/mat_vec_mul.rs +++ b/russell_lab/src/matvec/mat_vec_mul.rs @@ -4,7 +4,7 @@ use crate::{to_i32, StrError, CBLAS_COL_MAJOR, CBLAS_NO_TRANS}; extern "C" { // Performs one of the matrix-vector multiplication - // + // fn c_dgesv( n: *const i32, nrhs: *const i32, diff --git a/russell_lab/src/matvec/vec_mat_mul.rs b/russell_lab/src/matvec/vec_mat_mul.rs index da9a6ef8..b9aaa2f3 100644 --- a/russell_lab/src/matvec/vec_mat_mul.rs +++ b/russell_lab/src/matvec/vec_mat_mul.rs @@ -4,7 +4,7 @@ use crate::{to_i32, StrError, CBLAS_COL_MAJOR, CBLAS_TRANS}; extern "C" { // Performs one of the matrix-vector multiplication - // + // fn cblas_dger( layout: i32, m: i32, diff --git a/russell_lab/src/vector/aliases.rs b/russell_lab/src/vector/aliases.rs index 0cac7301..5010334c 100644 --- a/russell_lab/src/vector/aliases.rs +++ b/russell_lab/src/vector/aliases.rs @@ -1,8 +1,8 @@ use crate::NumVector; use num_complex::Complex64; -/// Vector is an alias to NumVector<f64> and is used in most functions that call OpenBLAS +/// Defines an alias to NumVector with f64 pub type Vector = NumVector; -/// ComplexVector is an alias to NumVector<Complex64> and is used in most functions that call OpenBLAS +/// Defines an alias to NumVector with Complex64 pub type ComplexVector = NumVector; diff --git a/russell_lab/src/vector/complex_vec_norm.rs b/russell_lab/src/vector/complex_vec_norm.rs new file mode 100644 index 00000000..205170e6 --- /dev/null +++ b/russell_lab/src/vector/complex_vec_norm.rs @@ -0,0 +1,122 @@ +use super::ComplexVector; +use crate::{to_i32, Norm}; +use num_complex::Complex64; + +extern "C" { + // Computes the Euclidean norm + // + fn cblas_dznrm2(n: i32, x: *const Complex64, incx: i32) -> f64; +} + +/// Returns the vector norm (complex version) +/// +/// Here: +/// +/// ```text +/// abs(z) = |z| = z.norm() = sqrt(z.real² + z.imag²) +/// ``` +/// +/// Euclidean-norm (Euc or Fro): +/// +/// ```text +/// ‖u‖_2 = sqrt(Σ_i uᵢ ⋅ conjugate(uᵢ)) +/// ``` +/// +/// Max-norm (Max or Inf): +/// +/// ```text +/// ‖u‖_max = max_i ( |uᵢ| ) == ‖u‖_∞ +/// ``` +/// +/// 1-norm (One, taxicab or sum of abs values): +/// +/// ```text +/// ‖u‖_1 := sum_i |uᵢ| +/// ``` +/// +/// # Example +/// +/// ``` +/// use num_complex::Complex64; +/// use russell_lab::{approx_eq, complex_vec_norm, Norm, ComplexVector, cpx}; +/// +/// fn main() { +/// let u = ComplexVector::from(&[cpx!(1.0, 1.0), cpx!(3.0, 1.0), cpx!(5.0, -1.0)]); +/// approx_eq(complex_vec_norm(&u, Norm::One), 9.67551073613426, 1e-15); +/// approx_eq(complex_vec_norm(&u, Norm::Euc), 6.164414002968976, 1e-15); +/// approx_eq(complex_vec_norm(&u, Norm::Max), 5.099019513592784, 1e-15); +/// } +/// ``` +pub fn complex_vec_norm(v: &ComplexVector, kind: Norm) -> f64 { + let n = to_i32(v.dim()); + if n == 0 { + return 0.0; + } + unsafe { + match kind { + Norm::Euc | Norm::Fro => cblas_dznrm2(n, v.as_data().as_ptr(), 1), + Norm::Inf | Norm::Max => { + let mut max = 0.0; + for i in 0..v.dim() { + let abs = v[i].norm(); + if abs > max { + max = abs; + } + } + max + } + Norm::One => v.as_data().iter().fold(0.0, |acc, z| acc + z.norm()), + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::complex_vec_norm; + use crate::{approx_eq, cpx, ComplexVector, Norm}; + use num_complex::Complex64; + + #[test] + fn complex_vec_norm_works() { + let u0 = ComplexVector::new(0); + assert_eq!(complex_vec_norm(&u0, Norm::Euc), 0.0); + assert_eq!(complex_vec_norm(&u0, Norm::Fro), 0.0); + assert_eq!(complex_vec_norm(&u0, Norm::Inf), 0.0); + assert_eq!(complex_vec_norm(&u0, Norm::Max), 0.0); + assert_eq!(complex_vec_norm(&u0, Norm::One), 0.0); + + let u = ComplexVector::from(&[ + cpx!(-3.0, 0.0), + cpx!(2.0, 0.0), + cpx!(1.0, 0.0), + cpx!(1.0, 0.0), + cpx!(1.0, 0.0), + ]); + assert_eq!(complex_vec_norm(&u, Norm::Euc), 4.0); + assert_eq!(complex_vec_norm(&u, Norm::Fro), 4.0); + assert_eq!(complex_vec_norm(&u, Norm::Inf), 3.0); + assert_eq!(complex_vec_norm(&u, Norm::Max), 3.0); + assert_eq!(complex_vec_norm(&u, Norm::One), 8.0); + + let u = ComplexVector::from(&[ + cpx!(-3.0, 1.0), + cpx!(2.0, -1.0), + cpx!(1.0, 1.0), + cpx!(1.0, -1.0), + cpx!(1.0, 1.0), + ]); + approx_eq(complex_vec_norm(&u, Norm::Euc), 4.58257569495584, 1e-15); + approx_eq(complex_vec_norm(&u, Norm::Fro), 4.58257569495584, 1e-15); + approx_eq(complex_vec_norm(&u, Norm::Inf), 3.1622776601683795, 1e-15); + approx_eq(complex_vec_norm(&u, Norm::Max), 3.1622776601683795, 1e-15); + approx_eq(complex_vec_norm(&u, Norm::One), 9.640986324787455, 1e-15); + + // example from https://netlib.org/lapack/lug/node75.html + let diff = ComplexVector::from(&[cpx!(-0.1, 0.0), cpx!(1.0, 0.0), cpx!(-2.0, 0.0)]); + approx_eq(complex_vec_norm(&diff, Norm::Euc), 2.238, 0.001); + assert_eq!(complex_vec_norm(&diff, Norm::Inf), 2.0); + assert_eq!(complex_vec_norm(&diff, Norm::One), 3.1); + } +} diff --git a/russell_lab/src/vector/complex_vec_unzip.rs b/russell_lab/src/vector/complex_vec_unzip.rs new file mode 100644 index 00000000..3729cbb2 --- /dev/null +++ b/russell_lab/src/vector/complex_vec_unzip.rs @@ -0,0 +1,81 @@ +use crate::ComplexVector; +use crate::StrError; +use crate::Vector; + +/// Zips two arrays (real and imag) to make a new ComplexVector +/// +/// # Example +/// +/// ``` +/// use russell_lab::*; +/// use num_complex::Complex64; +/// +/// fn main() -> Result<(), StrError> { +/// let v = ComplexVector::from(&[cpx!(1.0, 0.1), cpx!(2.0, 0.2), cpx!(3.0, 0.3)]); +/// let mut real = Vector::new(3); +/// let mut imag = Vector::new(3); +/// complex_vec_unzip(&mut real, &mut imag, &v)?; +/// assert_eq!( +/// format!("{}", real), +/// "┌ ┐\n\ +/// │ 1 │\n\ +/// │ 2 │\n\ +/// │ 3 │\n\ +/// └ ┘" +/// ); +/// assert_eq!( +/// format!("{}", imag), +/// "┌ ┐\n\ +/// │ 0.1 │\n\ +/// │ 0.2 │\n\ +/// │ 0.3 │\n\ +/// └ ┘" +/// ); +/// Ok(()) +/// } +/// ``` +pub fn complex_vec_unzip(real: &mut Vector, imag: &mut Vector, v: &ComplexVector) -> Result<(), StrError> { + let dim = v.dim(); + if real.dim() != dim || imag.dim() != dim { + return Err("vectors are incompatible"); + } + for i in 0..dim { + real[i] = v[i].re; + imag[i] = v[i].im; + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::complex_vec_unzip; + use crate::{cpx, vec_approx_eq, ComplexVector, Vector}; + use num_complex::Complex64; + + #[test] + fn complex_vec_unzip_handles_errors() { + let v = ComplexVector::new(2); + let mut wrong = Vector::new(1); + let mut ok = Vector::new(2); + assert_eq!( + complex_vec_unzip(&mut wrong, &mut ok, &v).err(), + Some("vectors are incompatible") + ); + assert_eq!( + complex_vec_unzip(&mut ok, &mut wrong, &v).err(), + Some("vectors are incompatible") + ); + } + + #[test] + fn complex_vec_unzip_works() { + let v = ComplexVector::from(&[cpx!(1.0, 4.0), cpx!(2.0, 5.0), cpx!(3.0, 6.0)]); + let mut real = Vector::new(3); + let mut imag = Vector::new(3); + complex_vec_unzip(&mut real, &mut imag, &v).unwrap(); + vec_approx_eq(real.as_data(), &[1.0, 2.0, 3.0], 1e-15); + vec_approx_eq(imag.as_data(), &[4.0, 5.0, 6.0], 1e-15); + } +} diff --git a/russell_lab/src/vector/complex_vec_update.rs b/russell_lab/src/vector/complex_vec_update.rs new file mode 100644 index 00000000..c36b4f92 --- /dev/null +++ b/russell_lab/src/vector/complex_vec_update.rs @@ -0,0 +1,83 @@ +use super::ComplexVector; +use crate::{to_i32, StrError}; +use num_complex::Complex64; + +extern "C" { + // Computes constant times a vector plus a vector (Complex version) + // + // see also /usr/include/x86_64-linux-gnu/cblas.h + fn cblas_zaxpy(n: i32, alpha: *const Complex64, x: *const Complex64, incx: i32, y: *mut Complex64, incy: i32); +} + +/// Updates vector based on another vector (Complex version) +/// +/// ```text +/// v += α⋅u +/// ``` +/// +/// # Example +/// +/// ``` +/// use russell_lab::{cpx, complex_vec_update, ComplexVector, StrError}; +/// use num_complex::Complex64; +/// +/// fn main() -> Result<(), StrError> { +/// let u = ComplexVector::from(&[10.0, 20.0, 30.0]); +/// let mut v = ComplexVector::from(&[10.0, 20.0, 30.0]); +/// complex_vec_update(&mut v, cpx!(0.1, 0.0), &u)?; +/// let correct = "┌ ┐\n\ +/// │ 11+0i │\n\ +/// │ 22+0i │\n\ +/// │ 33+0i │\n\ +/// └ ┘"; +/// assert_eq!(format!("{}", v), correct); +/// Ok(()) +/// } +/// ``` +pub fn complex_vec_update(v: &mut ComplexVector, alpha: Complex64, u: &ComplexVector) -> Result<(), StrError> { + let n = v.dim(); + if u.dim() != n { + return Err("vectors are incompatible"); + } + let n_i32 = to_i32(n); + unsafe { + cblas_zaxpy(n_i32, &alpha, u.as_data().as_ptr(), 1, v.as_mut_data().as_mut_ptr(), 1); + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::complex_vec_update; + use crate::{complex_vec_approx_eq, cpx, ComplexVector}; + use num_complex::Complex64; + + #[test] + fn complex_vec_update_fails_on_wrong_dims() { + let u = ComplexVector::new(4); + let mut v = ComplexVector::new(3); + assert_eq!( + complex_vec_update(&mut v, cpx!(2.0, 0.0), &u), + Err("vectors are incompatible") + ); + } + + #[test] + fn complex_vec_update_works() { + // real only + let u = ComplexVector::from(&[cpx!(10.0, 0.0), cpx!(20.0, 0.0), cpx!(30.0, 0.0)]); + let mut v = ComplexVector::from(&[cpx!(100.0, 0.0), cpx!(200.0, 0.0), cpx!(300.0, 0.0)]); + complex_vec_update(&mut v, cpx!(2.0, 0.0), &u).unwrap(); + let correct = &[cpx!(120.0, 0.0), cpx!(240.0, 0.0), cpx!(360.0, 0.0)]; + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + + // real and imag + let u = ComplexVector::from(&[cpx!(10.0, 3.0), cpx!(20.0, 2.0), cpx!(30.0, 1.0)]); + let mut v = ComplexVector::from(&[cpx!(100.0, 30.0), cpx!(200.0, 20.0), cpx!(300.0, 10.0)]); + complex_vec_update(&mut v, cpx!(2.0, -2.0), &u).unwrap(); + let correct = &[cpx!(126.0, 16.0), cpx!(244.0, -16.0), cpx!(362.0, -48.0)]; + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + } +} diff --git a/russell_lab/src/vector/complex_vec_zip.rs b/russell_lab/src/vector/complex_vec_zip.rs index 11b028eb..e9a259e3 100644 --- a/russell_lab/src/vector/complex_vec_zip.rs +++ b/russell_lab/src/vector/complex_vec_zip.rs @@ -10,9 +10,10 @@ use crate::Vector; /// use russell_lab::*; /// /// fn main() -> Result<(), StrError> { +/// let mut v = ComplexVector::new(3); /// let real = Vector::from(&[1.0, 2.0, 3.0]); /// let imag = Vector::from(&[0.1, 0.2, 0.3]); -/// let v = complex_vec_zip(&real, &imag)?; +/// complex_vec_zip(&mut v, &real, &imag)?; /// assert_eq!( /// format!("{}", v), /// "┌ ┐\n\ @@ -24,17 +25,16 @@ use crate::Vector; /// Ok(()) /// } /// ``` -pub fn complex_vec_zip(real: &Vector, imag: &Vector) -> Result { - let n = real.dim(); - if imag.dim() != n { - return Err("arrays are incompatible"); +pub fn complex_vec_zip(v: &mut ComplexVector, real: &Vector, imag: &Vector) -> Result<(), StrError> { + let dim = v.dim(); + if real.dim() != dim || imag.dim() != dim { + return Err("vectors are incompatible"); } - let mut v = ComplexVector::new(n); - for i in 0..n { + for i in 0..dim { v[i].re = real[i]; v[i].im = imag[i]; } - Ok(v) + Ok(()) } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -42,13 +42,20 @@ pub fn complex_vec_zip(real: &Vector, imag: &Vector) -> Result f64 { @@ -1012,16 +1011,15 @@ mod tests { │ 3 │\n\ └ ┘" ); - // serialize - let mut serialized = Vec::new(); - let mut serializer = rmp_serde::Serializer::new(&mut serialized); - u.serialize(&mut serializer).unwrap(); - assert!(serialized.len() > 0); - // deserialize - let mut deserializer = rmp_serde::Deserializer::new(&serialized[..]); - let b: NumVector = Deserialize::deserialize(&mut deserializer).unwrap(); + + // serialize to json + let json = serde_json::to_string(&u).unwrap(); + assert_eq!(json, r#"{"data":[1.0,2.0,3.0]}"#); + + // deserialize from json + let from_json: NumVector = serde_json::from_str(&json).unwrap(); assert_eq!( - format!("{}", b), + format!("{}", from_json), "┌ ┐\n\ │ 1 │\n\ │ 2 │\n\ diff --git a/russell_lab/src/vector/vec_inner.rs b/russell_lab/src/vector/vec_inner.rs index afc324c2..7c4271cc 100644 --- a/russell_lab/src/vector/vec_inner.rs +++ b/russell_lab/src/vector/vec_inner.rs @@ -3,7 +3,7 @@ use crate::to_i32; extern "C" { // Calculates the dot product of two vectors - // + // fn cblas_ddot(n: i32, x: *const f64, incx: i32, y: *const f64, incy: i32) -> f64; } diff --git a/russell_lab/src/vector/vec_max_scaled.rs b/russell_lab/src/vector/vec_max_scaled.rs index bc905c7e..bc253f1b 100644 --- a/russell_lab/src/vector/vec_max_scaled.rs +++ b/russell_lab/src/vector/vec_max_scaled.rs @@ -31,7 +31,7 @@ mod tests { use super::{vec_max_scaled, Vector}; #[test] - fn vec_rms_error_works() { + fn vec_max_scaled_works() { let empty = Vector::new(0); assert_eq!(vec_max_scaled(&empty, &empty), 0.0); diff --git a/russell_lab/src/vector/vec_norm.rs b/russell_lab/src/vector/vec_norm.rs index ac8caaac..1afbc915 100644 --- a/russell_lab/src/vector/vec_norm.rs +++ b/russell_lab/src/vector/vec_norm.rs @@ -3,15 +3,15 @@ use crate::{to_i32, Norm}; extern "C" { // Computes the sum of the absolute values (1-norm or taxicab norm) - // + // fn cblas_dasum(n: i32, x: *const f64, incx: i32) -> f64; // Computes the Euclidean norm - // + // fn cblas_dnrm2(n: i32, x: *const f64, incx: i32) -> f64; // Finds the index of the maximum absolute value - // + // fn cblas_idamax(n: i32, x: *const f64, incx: i32) -> i32; } diff --git a/russell_lab/src/vector/vec_rms_scaled.rs b/russell_lab/src/vector/vec_rms_scaled.rs index f2319d1b..57a193b0 100644 --- a/russell_lab/src/vector/vec_rms_scaled.rs +++ b/russell_lab/src/vector/vec_rms_scaled.rs @@ -50,7 +50,7 @@ mod tests { use crate::math::SQRT_2_BY_3; #[test] - fn vec_rms_error_works() { + fn vec_rms_scaled_works() { let empty = Vector::new(0); assert_eq!(vec_rms_scaled(&empty, &empty, 1.0, 1.0), 0.0); diff --git a/russell_lab/src/vector/vec_scale.rs b/russell_lab/src/vector/vec_scale.rs index bb5e9180..9e471df3 100644 --- a/russell_lab/src/vector/vec_scale.rs +++ b/russell_lab/src/vector/vec_scale.rs @@ -3,7 +3,7 @@ use crate::to_i32; extern "C" { // Scales a vector by a constant - // + // fn cblas_dscal(n: i32, alpha: f64, x: *const f64, incx: i32); } diff --git a/russell_lab/src/vector/vec_update.rs b/russell_lab/src/vector/vec_update.rs index 4e5a8219..c3555203 100644 --- a/russell_lab/src/vector/vec_update.rs +++ b/russell_lab/src/vector/vec_update.rs @@ -3,7 +3,7 @@ use crate::{to_i32, StrError}; extern "C" { // Computes constant times a vector plus a vector. - // + // fn cblas_daxpy(n: i32, alpha: f64, x: *const f64, incx: i32, y: *mut f64, incy: i32); } diff --git a/russell_ode/Cargo.toml b/russell_ode/Cargo.toml new file mode 100644 index 00000000..d3227e92 --- /dev/null +++ b/russell_ode/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "russell_ode" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Solvers for Ordinary Differential Equations and Differential Algebraic Systems" +homepage = "https://github.com/cpmech/russell" +repository = "https://github.com/cpmech/russell" +documentation = "https://docs.rs/russell_ode" +readme = "README.md" +categories = ["mathematics", "science"] +keywords = ["differential equations", "numerical methods", "solver"] + +[dependencies] +russell_lab = { path = "../russell_lab", version = "0.8" } +russell_sparse = { path = "../russell_sparse", version = "0.8" } +num-complex = { version = "0.4", features = ["serde"] } + +[dev-dependencies] +serial_test = "3.0" diff --git a/russell_ode/README.md b/russell_ode/README.md new file mode 100644 index 00000000..0e9c2e5b --- /dev/null +++ b/russell_ode/README.md @@ -0,0 +1,37 @@ +# Russell ODE - Solvers for Ordinary Differential Equations and Differential Algebraic Systems + +_This crate is part of [Russell - Rust Scientific Library](https://github.com/cpmech/russell)_ + +## Contents + +* [Introduction](#introduction) +* [Installation](#installation) +* [Setting Cargo.toml](#cargo) +* [Examples](#examples) + +## Introduction + +Work in progress... + +## Installation + +## Setting Cargo.toml + +[![Crates.io](https://img.shields.io/crates/v/russell_ode.svg)](https://crates.io/crates/russell_ode) + +👆 Check the crate version and update your Cargo.toml accordingly: + +```toml +[dependencies] +russell_ode = "*" +``` + +## Examples + +See also: + +* [russell_ode/examples](https://github.com/cpmech/russell/tree/main/russell_ode/examples) + +### TODO + +TODO diff --git a/russell_ode/data/fortran_dop853_van_der_pol.txt b/russell_ode/data/fortran_dop853_van_der_pol.txt new file mode 100644 index 00000000..9db65958 --- /dev/null +++ b/russell_ode/data/fortran_dop853_van_der_pol.txt @@ -0,0 +1,29 @@ + +running dop853.f test +step = 0, x = 0.00, y = 2.000000000000000E+00 0.000000000000000E+00 +step = 75, x = 0.10, y = 1.931611417501184E+00 -7.070951240604973E-01 +step = 128, x = 0.20, y = 1.858493744364602E+00 -7.571048730691862E-01 +step = 177, x = 0.30, y = 1.779738552663070E+00 -8.207783428730331E-01 +step = 243, x = 0.40, y = 1.693630125574239E+00 -9.059325720860119E-01 +step = 316, x = 0.50, y = 1.597323684590663E+00 -1.028599021502069E+00 +step = 389, x = 0.60, y = 1.485399833444327E+00 -1.229027713191856E+00 +step = 462, x = 0.70, y = 1.344513388516560E+00 -1.655964097245392E+00 +step = 525, x = 0.80, y = 1.102733648413240E+00 -4.482889108091831E+00 +step = 631, x = 0.90, y = -1.959570969570627E+00 6.898639929483857E-01 +step = 687, x = 1.00, y = -1.888370653007531E+00 7.357375003150176E-01 +step = 738, x = 1.10, y = -1.812037940682048E+00 7.932568811217787E-01 +step = 795, x = 1.20, y = -1.729133704161278E+00 8.685148673473582E-01 +step = 867, x = 1.30, y = -1.637364675331517E+00 9.733118754352394E-01 +step = 940, x = 1.40, y = -1.532633806061223E+00 1.134615914816403E+00 +step =1014, x = 1.50, y = -1.406045188743373E+00 1.434684142405845E+00 +step =1085, x = 1.60, y = -1.227570508060995E+00 2.379082947865465E+00 +step =1186, x = 1.70, y = 1.986867061114394E+00 -6.739232474182355E-01 +step =1246, x = 1.80, y = 1.917429871210668E+00 -7.162099941399320E-01 +step =1298, x = 1.90, y = 1.843292936171062E+00 -7.685214701753735E-01 +DoPri8: Dormand-Prince method (explicit, order 8(5,3), embedded) +Number of function evaluations = 21553 +Number of performed steps = 1469 +Number of accepted steps = 1348 +Number of rejected steps = 121 +y = 1.763234540172087E+00 -8.356886819301910E-01 +h = 8.656983588595286E-04 diff --git a/russell_ode/data/fortran_dop853_van_der_pol_debug.txt b/russell_ode/data/fortran_dop853_van_der_pol_debug.txt new file mode 100644 index 00000000..9f4deb4f --- /dev/null +++ b/russell_ode/data/fortran_dop853_van_der_pol_debug.txt @@ -0,0 +1,247 @@ + +running dop853.f test +step(A) = 1, err = 1.959638663289740E-12, h_new = 6.000000000000001E-04, n_yes = 0, n_no = 1, h*lambda = 0.000000000000000E+00 +step(A) = 2, err = 2.955980245499226E-06, h_new = 2.651893439400644E-03, n_yes = 0, n_no = 2, h*lambda = 5.999638637675994E-01 +step(A) = 3, err = 3.289190833225518E-01, h_new = 2.742599827984791E-03, n_yes = 0, n_no = 3, h*lambda = 2.647684181446536E+00 +step(A) = 4, err = 2.963282904871433E-02, h_new = 3.832068947656679E-03, n_yes = 0, n_no = 4, h*lambda = 2.731697216875155E+00 +step(A) = 5, err = 1.765232930466588E-02, h_new = 5.712491558181956E-03, n_yes = 0, n_no = 5, h*lambda = 3.803790881529834E+00 +step(A) = 6, err = 1.357237168717890E-08, h_new = 3.427494934909174E-02, n_yes = 0, n_no = 6, h*lambda = 5.641296505768319E+00 +step(R) = 7, err = 3.049983581528165E+02, h_new = 1.508975343207560E-02 +step(R) = 8, err = 2.753687427000322E+01, h_new = 8.972951547905639E-03 +step(A) = 9, err = 1.190643609945584E-01, h_new = 1.053671198224488E-02, n_yes = 1, n_no = 0, h*lambda = 8.789351829648975E+00 +step(R) = 10, err = 3.891356809844428E+00, h_new = 6.814205085743480E-03 +step(A) = 11, err = 1.254149001555174E-01, h_new = 7.949943966746855E-03, n_yes = 2, n_no = 0, h*lambda = 6.633281260805703E+00 +step(A) = 12, err = 1.807919811765532E-01, h_new = 7.594694594261238E-03, n_yes = 3, n_no = 0, h*lambda = 6.591787703063471E+00 +step(R) = 13, err = 1.084024659426730E+00, h_new = 6.766637768784153E-03 +step(A) = 14, err = 2.161217856127681E-01, h_new = 7.375273321472055E-03, n_yes = 4, n_no = 0, h*lambda = 6.504692821305968E+00 +step(A) = 15, err = 2.488521029659334E-01, h_new = 7.246407845260238E-03, n_yes = 5, n_no = 0, h*lambda = 6.463674121840817E+00 +step(A) = 16, err = 7.435797934263277E-01, h_new = 6.767828353418105E-03, n_yes = 6, n_no = 0, h*lambda = 6.874624514790060E+00 +step(A) = 17, err = 5.600360888226632E-01, h_new = 6.548846985852363E-03, n_yes = 7, n_no = 0, h*lambda = 6.379539619848891E+00 +step(A) = 18, err = 2.689360370853225E-01, h_new = 6.945463407347431E-03, n_yes = 8, n_no = 0, h*lambda = 6.134291480727791E+00 +step(A) = 19, err = 4.737739730192550E-01, h_new = 6.862736705124246E-03, n_yes = 9, n_no = 0, h*lambda = 6.462359077921882E+00 +step(A) = 20, err = 4.081931733449650E-01, h_new = 6.908466077183188E-03, n_yes = 10, n_no = 0, h*lambda = 6.342614488854702E+00 +step(A) = 21, err = 3.936599445388877E-01, h_new = 6.986086931314706E-03, n_yes = 11, n_no = 0, h*lambda = 6.341752994061589E+00 +step(A) = 22, err = 4.078712131210029E-01, h_new = 7.033331916449623E-03, n_yes = 12, n_no = 0, h*lambda = 6.368533582524074E+00 +step(A) = 23, err = 4.114534445404955E-01, h_new = 7.073160854625421E-03, n_yes = 13, n_no = 0, h*lambda = 6.366738680516228E+00 +step(A) = 24, err = 4.033631445442230E-01, h_new = 7.130894601877946E-03, n_yes = 14, n_no = 0, h*lambda = 6.357033171613826E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 1.141460201067603E-01, ACCEPTED STEP = 21 +step(A) = 25, err = 4.077460795117875E-01, h_new = 7.179394245392615E-03, n_yes = 15, n_no = 0, h*lambda = 6.362627602473077E+00 +step(A) = 26, err = 4.037854318248442E-01, h_new = 7.237048469636972E-03, n_yes = 16, n_no = 0, h*lambda = 6.358575193217763E+00 +step(A) = 27, err = 4.074225933954899E-01, h_new = 7.286992996411961E-03, n_yes = 17, n_no = 0, h*lambda = 6.361764246185123E+00 +step(A) = 28, err = 4.025036243432588E-01, h_new = 7.348431279766412E-03, n_yes = 18, n_no = 0, h*lambda = 6.356714697192896E+00 +step(A) = 29, err = 4.066531572312910E-01, h_new = 7.400893045571465E-03, n_yes = 19, n_no = 0, h*lambda = 6.360744201444202E+00 +step(A) = 30, err = 4.015562150819599E-01, h_new = 7.465490436421224E-03, n_yes = 20, n_no = 0, h*lambda = 6.355444820557152E+00 +step(A) = 31, err = 4.058232560149916E-01, h_new = 7.520708164291287E-03, n_yes = 21, n_no = 0, h*lambda = 6.359535584820661E+00 +step(A) = 32, err = 4.004792807135695E-01, h_new = 7.588898415478732E-03, n_yes = 22, n_no = 0, h*lambda = 6.353975475377664E+00 +step(A) = 33, err = 4.049042413246206E-01, h_new = 7.647195765549171E-03, n_yes = 23, n_no = 0, h*lambda = 6.358248130028030E+00 +step(A) = 34, err = 3.993867275576406E-01, h_new = 7.719168373327975E-03, n_yes = 24, n_no = 0, h*lambda = 6.352456077046539E+00 +step(A) = 35, err = 4.038828131676189E-01, h_new = 7.780922719248015E-03, n_yes = 25, n_no = 0, h*lambda = 6.356790279382604E+00 +step(A) = 36, err = 3.981946527794150E-01, h_new = 7.857089195073108E-03, n_yes = 26, n_no = 0, h*lambda = 6.350804544551124E+00 +step(A) = 37, err = 4.027908750123589E-01, h_new = 7.922627551144631E-03, n_yes = 27, n_no = 0, h*lambda = 6.355226958151572E+00 +step(A) = 38, err = 3.968989618768802E-01, h_new = 8.003441114451426E-03, n_yes = 28, n_no = 0, h*lambda = 6.349002719814372E+00 +step(A) = 39, err = 4.016091857812493E-01, h_new = 8.073164627669244E-03, n_yes = 29, n_no = 0, h*lambda = 6.353526548383705E+00 +step(A) = 40, err = 3.954854478243182E-01, h_new = 8.159151639133593E-03, n_yes = 30, n_no = 0, h*lambda = 6.347032595169746E+00 +step(A) = 41, err = 4.003287180194667E-01, h_new = 8.233517650455054E-03, n_yes = 31, n_no = 0, h*lambda = 6.351673639113059E+00 +step(A) = 42, err = 3.939366752571402E-01, h_new = 8.325294943726226E-03, n_yes = 32, n_no = 0, h*lambda = 6.344867851517789E+00 +step(A) = 43, err = 3.989362262003876E-01, h_new = 8.404835221718525E-03, n_yes = 33, n_no = 0, h*lambda = 6.349646213311302E+00 +step(A) = 44, err = 3.922322406642030E-01, h_new = 8.503129665852644E-03, n_yes = 34, n_no = 0, h*lambda = 6.342478281345484E+00 +step(A) = 45, err = 3.974165319398817E-01, h_new = 8.588465393941008E-03, n_yes = 35, n_no = 0, h*lambda = 6.347418360429272E+00 +step(A) = 46, err = 3.903471745854931E-01, h_new = 8.694141405485170E-03, n_yes = 36, n_no = 0, h*lambda = 6.339826580596097E+00 +step(A) = 47, err = 3.957515763723521E-01, h_new = 8.786003607233700E-03, n_yes = 37, n_no = 0, h*lambda = 6.344958758570509E+00 +step(A) = 48, err = 3.882509034593817E-01, h_new = 8.900098790260436E-03, n_yes = 38, n_no = 0, h*lambda = 6.336866791986790E+00 +step(A) = 49, err = 3.939197892963724E-01, h_new = 8.999354543786694E-03, n_yes = 39, n_no = 0, h*lambda = 6.342229201048759E+00 +step(A) = 50, err = 3.859055072050792E-01, h_new = 9.123127602653778E-03, n_yes = 40, n_no = 0, h*lambda = 6.333541511512307E+00 +step(A) = 51, err = 3.918952251806554E-01, h_new = 9.230814257720642E-03, n_yes = 41, n_no = 0, h*lambda = 6.339182626112716E+00 +step(A) = 52, err = 3.832633846547456E-01, h_new = 9.365810261080880E-03, n_yes = 42, n_no = 0, h*lambda = 6.329778111191833E+00 +step(A) = 53, err = 3.896464417389354E-01, h_new = 9.483180686680590E-03, n_yes = 43, n_no = 0, h*lambda = 6.335760400824825E+00 +step(A) = 54, err = 3.802639546871396E-01, h_new = 9.631321714049920E-03, n_yes = 44, n_no = 0, h*lambda = 6.325483341323892E+00 +step(A) = 55, err = 3.871350479976269E-01, h_new = 9.759904944406365E-03, n_yes = 45, n_no = 0, h*lambda = 6.331888556119084E+00 +step(A) = 56, err = 3.768289131875920E-01, h_new = 9.923618730235782E-03, n_yes = 46, n_no = 0, h*lambda = 6.320535467755772E+00 +step(A) = 57, err = 3.843138180202694E-01, h_new = 1.006530245373337E-02, n_yes = 47, n_no = 0, h*lambda = 6.327472463992327E+00 +step(A) = 58, err = 3.728552563616889E-01, h_new = 1.024770949226123E-02, n_yes = 48, n_no = 0, h*lambda = 6.314772533647831E+00 +step(A) = 59, err = 3.811242574519738E-01, h_new = 1.040485400521445E-02, n_yes = 49, n_no = 0, h*lambda = 6.322389158961099E+00 +step(A) = 60, err = 3.682047477363228E-01, h_new = 1.061004747695080E-02, n_yes = 50, n_no = 0, h*lambda = 6.307974325744030E+00 +step(A) = 61, err = 3.774935233222885E-01, h_new = 1.078564566608427E-02, n_yes = 51, n_no = 0, h*lambda = 6.316476004708351E+00 +step(A) = 62, err = 3.626875322468118E-01, h_new = 1.101912423933182E-02, n_yes = 52, n_no = 0, h*lambda = 6.299833751000393E+00 +step(A) = 63, err = 3.733307278971418E-01, h_new = 1.121702975772993E-02, n_yes = 53, n_no = 0, h*lambda = 6.309513557016553E+00 +step(A) = 64, err = 3.560357713035250E-01, h_new = 1.148639315495337E-02, n_yes = 54, n_no = 0, h*lambda = 6.289909649186166E+00 +step(A) = 65, err = 3.685230977022650E-01, h_new = 1.171165033362278E-02, n_yes = 55, n_no = 0, h*lambda = 6.301198914779232E+00 +step(A) = 66, err = 3.478595229546738E-01, h_new = 1.202777013980060E-02, n_yes = 56, n_no = 0, h*lambda = 6.277545458237089E+00 +step(A) = 67, err = 3.629338985345094E-01, h_new = 1.228709419065767E-02, n_yes = 57, n_no = 0, h*lambda = 6.291103027921865E+00 +step(A) = 68, err = 3.375694802238602E-01, h_new = 1.266619879821853E-02, n_yes = 58, n_no = 0, h*lambda = 6.261721378448611E+00 +step(A) = 69, err = 3.564085412695703E-01, h_new = 1.296866573696537E-02, n_yes = 59, n_no = 0, h*lambda = 6.278600116502583E+00 +step(A) = 70, err = 3.242343501050571E-01, h_new = 1.343632277479681E-02, n_yes = 60, n_no = 0, h*lambda = 6.240767851985754E+00 +step(A) = 71, err = 3.488100813437398E-01, h_new = 1.379428863529635E-02, n_yes = 61, n_no = 0, h*lambda = 6.262747484631655E+00 +step(A) = 72, err = 3.063010456581432E-01, h_new = 1.439372690752747E-02, n_yes = 62, n_no = 0, h*lambda = 6.211764538341034E+00 +step(A) = 73, err = 3.401584201599718E-01, h_new = 1.482366578386025E-02, n_yes = 63, n_no = 0, h*lambda = 6.242077724426380E+00 +step(A) = 74, err = 2.810069968550686E-01, h_new = 1.563538150357789E-02, n_yes = 64, n_no = 0, h*lambda = 6.169147305416486E+00 +step(A) = 75, err = 3.311667961548223E-01, h_new = 1.615642033930710E-02, n_yes = 65, n_no = 0, h*lambda = 6.214256864081382E+00 +step(A) = 76, err = 2.430597376241931E-01, h_new = 1.735295837251806E-02, n_yes = 66, n_no = 0, h*lambda = 6.101034621215406E+00 +step(A) = 77, err = 3.256047954119587E-01, h_new = 1.796923901210033E-02, n_yes = 67, n_no = 0, h*lambda = 6.175753177258414E+00 +step(A) = 78, err = 1.815900523460503E-01, h_new = 2.001638695004108E-02, n_yes = 67, n_no = 1, h*lambda = 5.977717665587894E+00 +step(A) = 79, err = 3.447500895029070E-01, h_new = 2.057975257528611E-02, n_yes = 68, n_no = 0, h*lambda = 6.124456071176638E+00 +step(A) = 80, err = 7.494034684253342E-02, h_new = 2.560608578640618E-02, n_yes = 68, n_no = 1, h*lambda = 5.705940812368194E+00 +step(A) = 81, err = 6.648330029613858E-01, h_new = 2.425194833240622E-02, n_yes = 69, n_no = 0, h*lambda = 6.133098554590505E+00 +step(A) = 82, err = 5.871646231207660E-02, h_new = 3.110958076743863E-02, n_yes = 69, n_no = 1, h*lambda = 4.867384689284566E+00 +step(A) = 83, err = 1.224305409033219E-01, h_new = 3.640411243348058E-02, n_yes = 69, n_no = 2, h*lambda = 4.522823329851793E+00 +step(A) = 84, err = 7.040557969695867E-02, h_new = 4.565013556890047E-02, n_yes = 70, n_no = 0, h*lambda = 7.528227971169771E+00 +step(R) = 85, err = 3.615022310303625E+02, h_new = 1.967527033979558E-02 +step(A) = 86, err = 1.763775503304252E-01, h_new = 2.199671381094752E-02, n_yes = 70, n_no = 1, h*lambda = 1.307303765126028E+00 +step(R) = 87, err = 1.058639370802532E+03, h_new = 7.414292453421656E-03 +step(A) = 88, err = 8.193025328826009E-03, h_new = 1.216554823051712E-02, n_yes = 70, n_no = 2, h*lambda = 1.075904249959075E+00 +step(A) = 89, err = 3.247216552609690E-01, h_new = 7.680213897570433E-03, n_yes = 70, n_no = 3, h*lambda = 2.517281269948783E+00 +step(R) = 90, err = 4.696476076101794E+02, h_new = 3.203644448936211E-03 +step(A) = 91, err = 5.081123195791324E-01, h_new = 3.137919907885860E-03, n_yes = 70, n_no = 4, h*lambda = 9.330798214902337E-01 +step(R) = 92, err = 1.109363451506409E+01, h_new = 2.090499808978971E-03 +step(A) = 93, err = 2.131027857985464E-02, h_new = 3.043820241309415E-03, n_yes = 70, n_no = 5, h*lambda = 2.978925948900621E+00 +step(R) = 94, err = 1.008394292326365E+00, h_new = 1.879484914696249E-03 +step(A) = 95, err = 5.003903543931383E-01, h_new = 1.844453613420861E-03, n_yes = 0, n_no = 6, h*lambda = 1.780820386221682E+00 +step(A) = 96, err = 3.285995718087074E-03, h_new = 3.392554809898990E-03, n_yes = 0, n_no = 7, h*lambda = 1.841306956507960E+00 +step(R) = 97, err = 8.454091748140812E+00, h_new = 2.338223455082580E-03 +step(A) = 98, err = 4.898107639146253E-01, h_new = 2.300779406584214E-03, n_yes = 0, n_no = 8, h*lambda = 2.364459947871286E+00 +step(A) = 99, err = 1.400696001087453E-01, h_new = 2.647430115769552E-03, n_yes = 0, n_no = 9, h*lambda = 2.323978779541553E+00 +step(A) = 100, err = 4.137503866928380E-02, h_new = 3.547927304229437E-03, n_yes = 0, n_no = 10, h*lambda = 2.668153875692219E+00 +step(A) = 101, err = 2.165847585282912E-02, h_new = 5.155416245600121E-03, n_yes = 0, n_no = 11, h*lambda = 3.564561766954261E+00 +step(A) = 102, err = 2.127984117506283E-03, h_new = 1.001175463256056E-02, n_yes = 0, n_no = 12, h*lambda = 5.156008614706338E+00 +step(A) = 103, err = 4.021520223977747E-01, h_new = 1.009726896079593E-02, n_yes = 1, n_no = 0, h*lambda = 9.923844917727425E+00 +step(R) = 104, err = 5.101997776428038E+01, h_new = 5.558772508811375E-03 +step(A) = 105, err = 1.004589778260953E-02, h_new = 8.891454616236583E-03, n_yes = 1, n_no = 1, h*lambda = 5.482430585146485E+00 +step(A) = 106, err = 2.275060301211081E-03, h_new = 1.070526189573793E-02, n_yes = 1, n_no = 2, h*lambda = 5.454889805521367E+00 +step(R) = 107, err = 2.885978696859342E+00, h_new = 8.439236657067955E-03 +step(A) = 108, err = 2.070056790907456E-01, h_new = 9.248001524194572E-03, n_yes = 2, n_no = 0, h*lambda = 8.217908845733771E+00 +step(R) = 109, err = 3.266643901495132E+00, h_new = 6.550628206108270E-03 +step(A) = 110, err = 1.083509983474625E-01, h_new = 7.783435765718324E-03, n_yes = 3, n_no = 0, h*lambda = 6.340484772404460E+00 +step(A) = 111, err = 9.254395628318393E-02, h_new = 7.938381250603994E-03, n_yes = 4, n_no = 0, h*lambda = 6.302011941423816E+00 +step(R) = 112, err = 1.160859196036360E+00, h_new = 7.012566921305652E-03 +step(A) = 113, err = 2.256803420494697E-01, h_new = 7.602086585789731E-03, n_yes = 5, n_no = 0, h*lambda = 6.702343634595116E+00 +step(A) = 114, err = 3.633185222355420E-01, h_new = 7.162812549441357E-03, n_yes = 6, n_no = 0, h*lambda = 6.658065404312254E+00 +step(A) = 115, err = 7.447589964965239E-01, h_new = 6.688429065786689E-03, n_yes = 7, n_no = 0, h*lambda = 6.754678647746046E+00 +step(A) = 116, err = 4.270349561007837E-01, h_new = 6.695125258492039E-03, n_yes = 8, n_no = 0, h*lambda = 6.266807641803351E+00 +step(A) = 117, err = 3.241872230025924E-01, h_new = 6.936681101854841E-03, n_yes = 9, n_no = 0, h*lambda = 6.232733381749739E+00 +step(A) = 118, err = 4.312213324972082E-01, h_new = 6.935163576632131E-03, n_yes = 10, n_no = 0, h*lambda = 6.413926107808549E+00 +step(A) = 119, err = 4.187789015969542E-01, h_new = 6.959068626637126E-03, n_yes = 11, n_no = 0, h*lambda = 6.369070235207361E+00 +step(A) = 120, err = 3.975480475475240E-01, h_new = 7.028617761148532E-03, n_yes = 12, n_no = 0, h*lambda = 6.346897025088153E+00 +step(A) = 121, err = 4.084659132486355E-01, h_new = 7.074861747009606E-03, n_yes = 13, n_no = 0, h*lambda = 6.365529555499854E+00 +step(A) = 122, err = 4.060100732388404E-01, h_new = 7.126780218799708E-03, n_yes = 14, n_no = 0, h*lambda = 6.361635455485464E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 9.881349085950795E-01, ACCEPTED STEP = 109 +step(A) = 123, err = 4.079019965058998E-01, h_new = 7.174908986936162E-03, n_yes = 15, n_no = 0, h*lambda = 6.362085653492173E+00 +step(A) = 124, err = 4.032567986990441E-01, h_new = 7.233711661577681E-03, n_yes = 16, n_no = 0, h*lambda = 6.357788804581586E+00 +step(A) = 125, err = 4.075247138426195E-01, h_new = 7.283404987172566E-03, n_yes = 17, n_no = 0, h*lambda = 6.362071764095525E+00 +step(A) = 126, err = 4.026373250609086E-01, h_new = 7.344508107879011E-03, n_yes = 18, n_no = 0, h*lambda = 6.356878640375506E+00 +step(A) = 127, err = 4.066441324506645E-01, h_new = 7.396962385589742E-03, n_yes = 19, n_no = 0, h*lambda = 6.360703437525811E+00 +step(A) = 128, err = 4.015773441125634E-01, h_new = 7.461476393626732E-03, n_yes = 20, n_no = 0, h*lambda = 6.355482753882741E+00 +step(A) = 129, err = 4.058625590655167E-01, h_new = 7.516573440539908E-03, n_yes = 21, n_no = 0, h*lambda = 6.359594648476476E+00 +step(A) = 130, err = 4.005156654303061E-01, h_new = 7.584640069767726E-03, n_yes = 22, n_no = 0, h*lambda = 6.354023251466810E+00 +step(A) = 131, err = 4.049351179411302E-01, h_new = 7.642831857938843E-03, n_yes = 23, n_no = 0, h*lambda = 6.358291792682883E+00 +step(A) = 132, err = 3.994252920573956E-01, h_new = 7.714670282761915E-03, n_yes = 24, n_no = 0, h*lambda = 6.352510089445839E+00 +step(A) = 133, err = 4.039185189030711E-01, h_new = 7.776302712549671E-03, n_yes = 25, n_no = 0, h*lambda = 6.356841223960268E+00 +step(A) = 134, err = 3.982362204651791E-01, h_new = 7.852321505048351E-03, n_yes = 26, n_no = 0, h*lambda = 6.350862151023890E+00 +step(A) = 135, err = 4.028289394840012E-01, h_new = 7.917726566294398E-03, n_yes = 27, n_no = 0, h*lambda = 6.355281614091100E+00 +step(A) = 136, err = 3.969443649199136E-01, h_new = 7.998375772334330E-03, n_yes = 28, n_no = 0, h*lambda = 6.349065935360435E+00 +step(A) = 137, err = 4.016504420881893E-01, h_new = 8.067951562448000E-03, n_yes = 29, n_no = 0, h*lambda = 6.353586059912446E+00 +step(A) = 138, err = 3.955350426202229E-01, h_new = 8.153755244251135E-03, n_yes = 30, n_no = 0, h*lambda = 6.347101808549010E+00 +step(A) = 139, err = 4.003734967315674E-01, h_new = 8.227957034020754E-03, n_yes = 31, n_no = 0, h*lambda = 6.351738620835539E+00 +step(A) = 140, err = 3.939911328251167E-01, h_new = 8.319528592135658E-03, n_yes = 32, n_no = 0, h*lambda = 6.344944073888236E+00 +step(A) = 141, err = 3.989850010577038E-01, h_new = 8.398885426613493E-03, n_yes = 33, n_no = 0, h*lambda = 6.349717450916532E+00 +step(A) = 142, err = 3.922923068837588E-01, h_new = 8.496947646654797E-03, n_yes = 34, n_no = 0, h*lambda = 6.342562622246558E+00 +step(A) = 143, err = 3.974698582768102E-01, h_new = 8.582077396113908E-03, n_yes = 35, n_no = 0, h*lambda = 6.347496810177391E+00 +step(A) = 144, err = 3.904137727186877E-01, h_new = 8.687489546571957E-03, n_yes = 36, n_no = 0, h*lambda = 6.339920422762179E+00 +step(A) = 145, err = 3.958101134434840E-01, h_new = 8.779119156380757E-03, n_yes = 37, n_no = 0, h*lambda = 6.345045574618171E+00 +step(A) = 146, err = 3.883251695256497E-01, h_new = 8.892912321973546E-03, n_yes = 38, n_no = 0, h*lambda = 6.336971848328186E+00 +step(A) = 147, err = 3.939843275326930E-01, h_new = 8.991903794032542E-03, n_yes = 39, n_no = 0, h*lambda = 6.342325798329268E+00 +step(A) = 148, err = 3.859888573585522E-01, h_new = 9.115328304089620E-03, n_yes = 40, n_no = 0, h*lambda = 6.333659933820814E+00 +step(A) = 149, err = 3.919667192253866E-01, h_new = 9.222712601171424E-03, n_yes = 41, n_no = 0, h*lambda = 6.339290757261591E+00 +step(A) = 150, err = 3.833576079253681E-01, h_new = 9.357302597411634E-03, n_yes = 42, n_no = 0, h*lambda = 6.329912639361193E+00 +step(A) = 151, err = 3.897260517275788E-01, h_new = 9.474324461901461E-03, n_yes = 43, n_no = 0, h*lambda = 6.335882260436112E+00 +step(A) = 152, err = 3.803713435484557E-01, h_new = 9.621987520663242E-03, n_yes = 44, n_no = 0, h*lambda = 6.325637519545015E+00 +step(A) = 153, err = 3.872241957887284E-01, h_new = 9.750165509616092E-03, n_yes = 45, n_no = 0, h*lambda = 6.332026930594409E+00 +step(A) = 154, err = 3.769524534465372E-01, h_new = 9.913309733655513E-03, n_yes = 46, n_no = 0, h*lambda = 6.320713961201241E+00 +step(A) = 155, err = 3.844142587215670E-01, h_new = 1.005451783931602E-02, n_yes = 47, n_no = 0, h*lambda = 6.327630943162317E+00 +step(A) = 156, err = 3.729989106336811E-01, h_new = 1.023623653904493E-02, n_yes = 48, n_no = 0, h*lambda = 6.314981608071641E+00 +step(A) = 157, err = 3.812381708884542E-01, h_new = 1.039281688370112E-02, n_yes = 49, n_no = 0, h*lambda = 6.322572443295686E+00 +step(A) = 158, err = 3.683738871676462E-01, h_new = 1.059716460195157E-02, n_yes = 50, n_no = 0, h*lambda = 6.308222593956885E+00 +step(A) = 159, err = 3.776236289379404E-01, h_new = 1.077208556342378E-02, n_yes = 51, n_no = 0, h*lambda = 6.316690366547824E+00 +step(A) = 160, err = 3.628896196535183E-01, h_new = 1.100450432995160E-02, n_yes = 52, n_no = 0, h*lambda = 6.300133377250898E+00 +step(A) = 161, err = 3.734804154794846E-01, h_new = 1.120158595867956E-02, n_yes = 53, n_no = 0, h*lambda = 6.309767543248209E+00 +step(A) = 162, err = 3.562814773856994E-01, h_new = 1.146958937156355E-02, n_yes = 54, n_no = 0, h*lambda = 6.290278371489547E+00 +step(A) = 163, err = 3.686965460528878E-01, h_new = 1.169382918217471E-02, n_yes = 55, n_no = 0, h*lambda = 6.301504456023957E+00 +step(A) = 164, err = 3.481646290599441E-01, h_new = 1.200815192900144E-02, n_yes = 56, n_no = 0, h*lambda = 6.278010148914895E+00 +step(A) = 165, err = 3.631360706292755E-01, h_new = 1.226619910083735E-02, n_yes = 57, n_no = 0, h*lambda = 6.291477240395897E+00 +step(A) = 166, err = 3.379582856956321E-01, h_new = 1.264283971041080E-02, n_yes = 58, n_no = 0, h*lambda = 6.262324685769426E+00 +step(A) = 167, err = 3.566446940446351E-01, h_new = 1.294367710420577E-02, n_yes = 59, n_no = 0, h*lambda = 6.279068319108486E+00 +step(A) = 168, err = 3.247461179211537E-01, h_new = 1.340778952563008E-02, n_yes = 60, n_no = 0, h*lambda = 6.241581476243811E+00 +step(A) = 169, err = 3.490835837448948E-01, h_new = 1.376364666425348E-02, n_yes = 61, n_no = 0, h*lambda = 6.263348396502632E+00 +step(A) = 170, err = 3.070030098581453E-01, h_new = 1.435764448171737E-02, n_yes = 62, n_no = 0, h*lambda = 6.212918158244427E+00 +step(A) = 171, err = 3.404624903158653E-01, h_new = 1.478485418962688E-02, n_yes = 63, n_no = 0, h*lambda = 6.242872417612695E+00 +step(A) = 172, err = 2.820225026082013E-01, h_new = 1.558741452442216E-02, n_yes = 64, n_no = 0, h*lambda = 6.170898881415922E+00 +step(A) = 173, err = 3.314517487726826E-01, h_new = 1.610512333618712E-02, n_yes = 65, n_no = 0, h*lambda = 6.215342444019660E+00 +step(A) = 174, err = 2.446329688125062E-01, h_new = 1.728391776382303E-02, n_yes = 66, n_no = 0, h*lambda = 6.103967406267166E+00 +step(A) = 175, err = 3.256218238131412E-01, h_new = 1.789762946662598E-02, n_yes = 67, n_no = 0, h*lambda = 6.177263852592048E+00 +step(A) = 176, err = 1.842322066998068E-01, h_new = 1.990065303253532E-02, n_yes = 67, n_no = 1, h*lambda = 5.983408373653228E+00 +step(A) = 177, err = 3.429173867205635E-01, h_new = 2.047439837110997E-02, n_yes = 68, n_no = 0, h*lambda = 6.126272396714477E+00 +step(A) = 178, err = 7.954959625265076E-02, h_new = 2.528563770796823E-02, n_yes = 68, n_no = 1, h*lambda = 5.719907969586449E+00 +step(A) = 179, err = 6.268881100096639E-01, h_new = 2.412501913194669E-02, n_yes = 69, n_no = 0, h*lambda = 6.125818987017078E+00 +step(A) = 180, err = 5.092875291083272E-02, h_new = 3.150212102498735E-02, n_yes = 69, n_no = 1, h*lambda = 4.919484055217311E+00 +step(A) = 181, err = 1.868841443849700E-01, h_new = 3.496517836518934E-02, n_yes = 69, n_no = 2, h*lambda = 4.669241473223804E+00 +step(A) = 182, err = 3.326367005140728E-02, h_new = 4.815395770099203E-02, n_yes = 69, n_no = 3, h*lambda = 1.170798053973569E+00 +step(R) = 183, err = 3.842551388784302E+02, h_new = 2.059667118831029E-02 +step(A) = 184, err = 1.290014884507036E-01, h_new = 2.394502005451645E-02, n_yes = 69, n_no = 4, h*lambda = 1.037811969509999E+00 +step(R) = 185, err = 1.637439086078181E+03, h_new = 7.349687963259307E-03 +step(A) = 186, err = 3.387747535878958E-03, h_new = 1.346705180744629E-02, n_yes = 69, n_no = 5, h*lambda = 8.051118631407250E-01 +step(A) = 187, err = 2.392761264869706E-02, h_new = 1.054748677259432E-02, n_yes = 0, n_no = 6, h*lambda = 2.017037316009412E+00 +step(R) = 188, err = 5.883494026243961E+02, h_new = 4.277471102581100E-03 +step(A) = 189, err = 1.063381786564274E-02, h_new = 6.793497837570425E-03, n_yes = 0, n_no = 7, h*lambda = 1.268282519357536E+00 +step(R) = 190, err = 1.442587930668910E+02, h_new = 2.067934634023509E-03 +step(A) = 191, err = 5.499728092426808E-02, h_new = 2.674463431230688E-03, n_yes = 0, n_no = 8, h*lambda = 2.355334203844016E-01 +step(A) = 192, err = 1.744550738909289E-01, h_new = 2.315095259538681E-03, n_yes = 0, n_no = 9, h*lambda = 1.926758199851226E+00 +step(A) = 193, err = 3.666623735391872E-01, h_new = 2.361990148756619E-03, n_yes = 0, n_no = 10, h*lambda = 2.322097313427576E+00 +step(A) = 194, err = 4.677791748116633E-01, h_new = 2.337574607402342E-03, n_yes = 0, n_no = 11, h*lambda = 2.388609638868325E+00 +step(A) = 195, err = 2.521798830936710E-01, h_new = 2.499160715372906E-03, n_yes = 0, n_no = 12, h*lambda = 2.362450678797520E+00 +step(A) = 196, err = 3.993139473097741E-02, h_new = 3.364127018728178E-03, n_yes = 0, n_no = 13, h*lambda = 2.520556544738141E+00 +step(A) = 197, err = 2.745227117436805E-02, h_new = 4.745616173047884E-03, n_yes = 0, n_no = 14, h*lambda = 3.382933879894489E+00 +step(A) = 198, err = 4.695507521317095E-03, h_new = 8.347853162729110E-03, n_yes = 0, n_no = 15, h*lambda = 4.752191853774447E+00 +step(A) = 199, err = 3.874437752120564E-02, h_new = 1.127953574234764E-02, n_yes = 1, n_no = 0, h*lambda = 8.297479848166985E+00 +step(R) = 200, err = 1.788249104833765E+01, h_new = 7.079135614604501E-03 +step(A) = 201, err = 8.780937804839464E-02, h_new = 8.635353664314799E-03, n_yes = 2, n_no = 0, h*lambda = 6.991810121159718E+00 +step(A) = 202, err = 2.296072744807583E-01, h_new = 7.657720945088035E-03, n_yes = 3, n_no = 0, h*lambda = 6.947181426895245E+00 +step(R) = 203, err = 1.540362789576154E+00, h_new = 6.529638953185796E-03 +step(A) = 204, err = 1.623861307622340E-01, h_new = 7.375866727558742E-03, n_yes = 4, n_no = 0, h*lambda = 6.369816867435181E+00 +step(A) = 205, err = 1.471538083519933E-01, h_new = 7.467242226372809E-03, n_yes = 5, n_no = 0, h*lambda = 6.331749647294362E+00 +step(A) = 206, err = 9.091148569753943E-01, h_new = 6.801041179803955E-03, n_yes = 6, n_no = 0, h*lambda = 7.190873199670890E+00 +step(A) = 207, err = 8.586960162117802E-01, h_new = 6.238612095569134E-03, n_yes = 7, n_no = 0, h*lambda = 6.508063535411213E+00 +step(A) = 208, err = 1.522203722643146E-01, h_new = 7.104301674461251E-03, n_yes = 7, n_no = 1, h*lambda = 5.934729718342836E+00 +step(A) = 209, err = 8.064588489309779E-01, h_new = 6.568120742556456E-03, n_yes = 8, n_no = 0, h*lambda = 6.713004288885192E+00 +step(A) = 210, err = 3.428816463501300E-01, h_new = 6.757570844854359E-03, n_yes = 9, n_no = 0, h*lambda = 6.167297175289476E+00 +step(A) = 211, err = 3.716952066558486E-01, h_new = 6.882714369078041E-03, n_yes = 10, n_no = 0, h*lambda = 6.304099123188388E+00 +step(A) = 212, err = 4.108452573012789E-01, h_new = 6.922970348586769E-03, n_yes = 11, n_no = 0, h*lambda = 6.377862222260362E+00 +step(A) = 213, err = 4.156417345362928E-01, h_new = 6.953365963313394E-03, n_yes = 12, n_no = 0, h*lambda = 6.371888280463802E+00 +step(A) = 214, err = 4.032746931646385E-01, h_new = 7.010314075780550E-03, n_yes = 13, n_no = 0, h*lambda = 6.355835742997682E+00 +step(A) = 215, err = 4.080227444621372E-01, h_new = 7.057395212359654E-03, n_yes = 14, n_no = 0, h*lambda = 6.363351711621393E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 1.853542594920877E+00, ACCEPTED STEP = 196 +step(A) = 216, err = 4.051403260117802E-01, h_new = 7.111091449931478E-03, n_yes = 15, n_no = 0, h*lambda = 6.360565720889913E+00 +step(A) = 217, err = 4.083105985517257E-01, h_new = 7.158218347879135E-03, n_yes = 16, n_no = 0, h*lambda = 6.362957765869774E+00 +step(A) = 218, err = 4.035563235730730E-01, h_new = 7.216214456782076E-03, n_yes = 17, n_no = 0, h*lambda = 6.358114388856899E+00 +step(A) = 219, err = 4.075497194216475E-01, h_new = 7.265731855718158E-03, n_yes = 18, n_no = 0, h*lambda = 6.362067208141631E+00 +step(A) = 220, err = 4.027779730018146E-01, h_new = 7.326366856221879E-03, n_yes = 19, n_no = 0, h*lambda = 6.357094001500717E+00 +step(A) = 221, err = 4.067895963926024E-01, h_new = 7.378361699591563E-03, n_yes = 20, n_no = 0, h*lambda = 6.360918589785125E+00 +step(A) = 222, err = 4.017445107270828E-01, h_new = 7.442326291922512E-03, n_yes = 21, n_no = 0, h*lambda = 6.355702205513821E+00 +step(A) = 223, err = 4.059972145345996E-01, h_new = 7.496971061328855E-03, n_yes = 22, n_no = 0, h*lambda = 6.359782046983931E+00 +step(A) = 224, err = 4.006831844194843E-01, h_new = 7.564464765291072E-03, n_yes = 23, n_no = 0, h*lambda = 6.354257829357048E+00 +step(A) = 225, err = 4.050928333183331E-01, h_new = 7.622130738910705E-03, n_yes = 24, n_no = 0, h*lambda = 6.358516102599155E+00 +step(A) = 226, err = 3.996066820502495E-01, h_new = 7.693337951817826E-03, n_yes = 25, n_no = 0, h*lambda = 6.352760684694431E+00 +step(A) = 227, err = 4.040853787497352E-01, h_new = 7.754399609695395E-03, n_yes = 26, n_no = 0, h*lambda = 6.357079519999597E+00 +step(A) = 228, err = 3.984334159525326E-01, h_new = 7.829719756616404E-03, n_yes = 27, n_no = 0, h*lambda = 6.351136028902602E+00 +step(A) = 229, err = 4.030094038080632E-01, h_new = 7.894494561424074E-03, n_yes = 28, n_no = 0, h*lambda = 6.355540435749852E+00 +step(A) = 230, err = 3.971588512305952E-01, h_new = 7.974368643476979E-03, n_yes = 29, n_no = 0, h*lambda = 6.349364394057486E+00 +step(A) = 231, err = 4.018455938617868E-01, h_new = 8.043247204377182E-03, n_yes = 30, n_no = 0, h*lambda = 6.353867465375588E+00 +step(A) = 232, err = 3.957694751760499E-01, h_new = 8.128186114056779E-03, n_yes = 31, n_no = 0, h*lambda = 6.347428901129612E+00 +step(A) = 233, err = 4.005852761546830E-01, h_new = 8.201613056816588E-03, n_yes = 32, n_no = 0, h*lambda = 6.352045756675778E+00 +step(A) = 234, err = 3.942483877368551E-01, h_new = 8.292214821660902E-03, n_yes = 33, n_no = 0, h*lambda = 6.345304039670358E+00 +step(A) = 235, err = 6.469446879417725E-05, h_new = 6.908420682852039E-03, n_yes = 33, n_no = 1, h*lambda = 1.773473114548921E+00 +DoPri8: Dormand-Prince method (explicit, order 8(5,3), embedded) +Number of function evaluations = 2802 +Number of performed steps = 235 +Number of accepted steps = 215 +Number of rejected steps = 20 +y = 1.819907445729370E+00 -7.866363461162956E-01 +h = 6.908420682852039E-03 diff --git a/russell_ode/data/fortran_dopri5_arenstorf.txt b/russell_ode/data/fortran_dopri5_arenstorf.txt new file mode 100644 index 00000000..cc093cea --- /dev/null +++ b/russell_ode/data/fortran_dopri5_arenstorf.txt @@ -0,0 +1,27 @@ + +running dopri5.f test +step = 0, x = 0.00, y = 9.940000000000000E-01 0.000000000000000E+00 0.000000000000000E+00 -2.001585106379082E+00 +step = 47, x = 1.00, y = 3.132838429324978E-01 3.480091155210374E-01 -1.042618215839560E+00 6.733839248384575E-01 +step = 62, x = 2.00, y = -5.798780533208580E-01 6.090775695401189E-01 -4.225300697828488E-01 2.442220183559874E-01 +step = 69, x = 3.00, y = -6.223461712173137E-01 9.682675050145123E-01 2.788054332232925E-01 3.604394979561648E-01 +step = 75, x = 4.00, y = -1.983335162290172E-01 1.137638045474944E+00 4.486524729639033E-01 -6.688570025290282E-02 +step = 82, x = 5.00, y = 2.268867961342928E-02 8.665401519108538E-01 -1.177361292733348E-01 -4.217863140456828E-01 +step = 93, x = 6.00, y = -4.735744349581025E-01 2.239068009411740E-01 -5.609488561289041E-01 -9.861372030071190E-01 +step = 105, x = 7.00, y = -8.157792084388479E-01 -4.352867069540937E-01 -3.627947927416162E-01 -2.046655236855982E-01 +step = 112, x = 8.00, y = -1.174553319989362E+00 -2.759466957074702E-01 -2.531713451617611E-01 4.473768609449997E-01 +step = 118, x = 9.00, y = -1.190202656712924E+00 2.459675968232476E-01 2.264445030412396E-01 4.714257090528761E-01 +step = 124, x = 10.00, y = -8.398073586249510E-01 4.468300944741782E-01 3.737429154752904E-01 -1.496700318777275E-01 +step = 134, x = 11.00, y = -5.081113096374591E-01 -1.592073708750532E-01 4.978491909799694E-01 -9.944444141765330E-01 +step = 147, x = 12.00, y = 1.314719824382037E-02 -8.385751955510712E-01 1.752757009860339E-01 -4.358668845307599E-01 +step = 155, x = 13.00, y = -1.695504654604082E-01 -1.132227264074144E+00 -4.333572977280750E-01 -9.895216108651012E-02 +step = 160, x = 14.00, y = -6.031127884201314E-01 -9.912598152026094E-01 -3.104960656203913E-01 3.441899768169620E-01 +step = 167, x = 15.00, y = -6.055709668050926E-01 -6.258686162317906E-01 3.659167703110920E-01 2.704440021698044E-01 +step = 179, x = 16.00, y = 2.427114697971962E-01 -3.899946986994198E-01 1.118813953275476E+00 6.095882055275095E-01 +step = 197, x = 17.00, y = 9.413011551544933E-01 3.531669084568508E-02 6.983649876011137E-01 -1.853067154749635E-01 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 1430 +Number of performed steps = 238 +Number of accepted steps = 217 +Number of rejected steps = 21 +y = 9.940021704030663E-01 9.040891036151961E-06 1.459758305600828E-03 -2.001245515834718E+00 +h = 5.258587607119909E-04 diff --git a/russell_ode/data/fortran_dopri5_arenstorf_debug.txt b/russell_ode/data/fortran_dopri5_arenstorf_debug.txt new file mode 100644 index 00000000..ae8d156c --- /dev/null +++ b/russell_ode/data/fortran_dopri5_arenstorf_debug.txt @@ -0,0 +1,247 @@ + +running dopri5.f test +step(A) = 1, err = 1.296464663661232E-04, h_new = 2.851498799828624E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 2, err = 2.154808513298955E-02, h_new = 3.444662315267757E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 3, err = 4.641537941189766E-02, h_new = 4.481186168284893E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 4, err = 1.300205094695082E-01, h_new = 5.045682925517511E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 5, err = 1.638798157695572E-01, h_new = 5.691852147157127E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 6, err = 1.953077508608763E-01, h_new = 6.290060562224797E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 7, err = 2.037616912397756E-01, h_new = 6.949847649056909E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 8, err = 2.098121549295829E-01, h_new = 7.653700888000884E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 9, err = 2.094489828036394E-01, h_new = 8.441194697533934E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 10, err = 2.043646647710456E-01, h_new = 9.348040168659195E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 11, err = 1.958605808568633E-01, h_new = 1.041713554534016E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 12, err = 1.867806015283810E-01, h_new = 1.168267526938489E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 13, err = 1.784989570478211E-01, h_new = 1.317831910094379E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 14, err = 1.713743477920451E-01, h_new = 1.494160212667347E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 15, err = 1.651983995573856E-01, h_new = 1.701909715834980E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 16, err = 1.598307271494841E-01, h_new = 1.946601320501096E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 17, err = 1.548855195511171E-01, h_new = 2.235445546125211E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 18, err = 1.502839942980045E-01, h_new = 2.577103574694174E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 19, err = 1.459632473922305E-01, h_new = 2.982150066663001E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 20, err = 1.419097561700171E-01, h_new = 3.463376142970893E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 21, err = 1.381058593862838E-01, h_new = 4.036329414628691E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 22, err = 1.345336641969437E-01, h_new = 4.719938407830495E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 23, err = 1.311708636182164E-01, h_new = 5.537321319099569E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 24, err = 1.279946367901294E-01, h_new = 6.516781032515856E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 25, err = 1.249836646331388E-01, h_new = 7.693044698128378E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 26, err = 1.221206561857271E-01, h_new = 9.108791127380986E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 27, err = 1.193944038746160E-01, h_new = 1.081651963263788E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 28, err = 1.168020495540477E-01, h_new = 1.288079986329772E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 29, err = 1.143515180499088E-01, h_new = 1.538091710421759E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 30, err = 1.120637771818752E-01, h_new = 1.841388068358573E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 31, err = 1.099734920707503E-01, h_new = 2.209772078451624E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 32, err = 1.081253443642000E-01, h_new = 2.657503445099456E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 33, err = 1.060207988092988E-01, h_new = 3.204475559199388E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 34, err = 1.047664155510231E-01, h_new = 3.868809640221671E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 35, err = 1.050191701058794E-01, h_new = 4.666734516783002E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 36, err = 1.061519573881847E-01, h_new = 5.619511791381283E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 37, err = 1.129641636507217E-01, h_new = 6.698511372187405E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 38, err = 1.490676458558739E-01, h_new = 7.635953223325946E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 39, err = 2.552125339676862E-01, h_new = 8.032814990553558E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 40, err = 3.915024696506432E-01, h_new = 8.028261090842825E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 41, err = 5.175985420975771E-01, h_new = 7.783851825904252E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 42, err = 6.092736849372453E-01, h_new = 7.422986653963178E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 43, err = 6.609447309720450E-01, h_new = 7.027253352793006E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 44, err = 6.848737581756343E-01, h_new = 6.634083557012435E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 45, err = 7.001897671449597E-01, h_new = 6.248290150325034E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 46, err = 7.051668328685314E-01, h_new = 5.883052299790132E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 47, err = 6.890558376853408E-01, h_new = 5.562546352740977E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 48, err = 6.296642823202815E-01, h_new = 5.335778455164855E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 49, err = 5.462361627332699E-01, h_new = 5.224563170634317E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 50, err = 4.615041174526378E-01, h_new = 5.234532051912473E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 51, err = 3.901163295168728E-01, h_new = 5.360241959578553E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 52, err = 3.312625460215473E-01, h_new = 5.605900325812620E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 53, err = 2.947697247096200E-01, h_new = 5.941316463041803E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 54, err = 2.847043236557166E-01, h_new = 6.304599735557324E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 55, err = 2.858210232378161E-01, h_new = 6.676360554079146E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 56, err = 2.858704446985239E-01, h_new = 7.070942164522676E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 57, err = 2.791391843519196E-01, h_new = 7.519293343154304E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 58, err = 2.683971735131291E-01, h_new = 8.041926880782167E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 59, err = 2.581463156609763E-01, h_new = 8.644433520816096E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 60, err = 2.499502450561518E-01, h_new = 9.328645272242231E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 61, err = 2.455448506324762E-01, h_new = 1.008446794691735E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 62, err = 2.458366585290539E-01, h_new = 1.089157792612123E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 63, err = 2.514770032139527E-01, h_new = 1.171856602583296E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 64, err = 2.624735247662983E-01, h_new = 1.252830668842625E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 65, err = 2.779076268527033E-01, h_new = 1.328725368021894E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 66, err = 2.956697986365311E-01, h_new = 1.397644019645542E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 67, err = 2.887912364348064E-01, h_new = 1.479694559118314E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 68, err = 2.893388996943028E-01, h_new = 1.564583645902221E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 69, err = 2.976619060082398E-01, h_new = 1.646510918290760E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 70, err = 3.106968255117830E-01, h_new = 1.722101702134755E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 71, err = 3.367687753502823E-01, h_new = 1.779706428153806E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 72, err = 3.864259984727088E-01, h_new = 1.802531545106097E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 73, err = 4.537563399690108E-01, h_new = 1.786274410422178E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 74, err = 5.117370090190735E-01, h_new = 1.745523233926182E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 75, err = 5.435151838740435E-01, h_new = 1.696461587902954E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 76, err = 5.669452536001536E-01, h_new = 1.640941188728515E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 77, err = 5.865556442105303E-01, h_new = 1.580755143224618E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 78, err = 6.130968712564684E-01, h_new = 1.513420229324444E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 79, err = 6.506815514632983E-01, h_new = 1.436913298844803E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 80, err = 7.015363880645173E-01, h_new = 1.350141476443922E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 81, err = 7.192280373074790E-01, h_new = 1.267057935032376E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 82, err = 7.008369338083363E-01, h_new = 1.195525269424596E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 83, err = 7.283986596946883E-01, h_new = 1.119497673305478E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 84, err = 7.435047659544324E-01, h_new = 1.046266286989278E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 85, err = 7.568229179243304E-01, h_new = 9.756792404896651E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 86, err = 7.616522238862367E-01, h_new = 9.095167541431626E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 87, err = 7.707728197331979E-01, h_new = 8.463421980695239E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 88, err = 7.981918259589843E-01, h_new = 7.832624797386101E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 89, err = 8.230117231601290E-01, h_new = 7.221295219024462E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 90, err = 7.851702623226555E-01, h_new = 6.719392296127329E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 91, err = 6.997284567643505E-01, h_new = 6.364042028409270E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 92, err = 5.918024978032125E-01, h_new = 6.173090941701424E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 93, err = 4.899521996029822E-01, h_new = 6.141947858481472E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 94, err = 4.052105891379512E-01, h_new = 6.263961063540265E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 95, err = 3.409407803907692E-01, h_new = 6.528950391335049E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 96, err = 2.982438894043942E-01, h_new = 6.913785030816562E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 97, err = 2.689044941720832E-01, h_new = 7.411559052967935E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 98, err = 2.666962225628043E-01, h_new = 7.923428407383011E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 99, err = 2.744209024757484E-01, h_new = 8.426852653619575E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 100, err = 2.775707991805772E-01, h_new = 8.955112689050265E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 101, err = 2.751111676416165E-01, h_new = 9.535250751844487E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 102, err = 2.716483607638056E-01, h_new = 1.017123647474220E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 103, err = 2.706064173222094E-01, h_new = 1.085123250237267E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 104, err = 2.743806133543793E-01, h_new = 1.154768743525360E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 105, err = 2.838995836842110E-01, h_new = 1.222457186728658E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 106, err = 2.989933096025295E-01, h_new = 1.284518335171625E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 107, err = 3.182974395281981E-01, h_new = 1.338220385572336E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 108, err = 2.884695079998747E-01, h_new = 1.421236954194553E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 109, err = 2.979575067144381E-01, h_new = 1.495225726777838E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 110, err = 3.044004483786207E-01, h_new = 1.569385893645763E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 111, err = 3.175699202861367E-01, h_new = 1.636806625816022E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 112, err = 3.334744125332794E-01, h_new = 1.695871065775893E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 113, err = 3.592470455679707E-01, h_new = 1.738365105361087E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 114, err = 3.994700220723698E-01, h_new = 1.755282085570384E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 115, err = 4.503645539387915E-01, h_new = 1.743985554337888E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 116, err = 4.353794119248903E-01, h_new = 1.751138191043228E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 117, err = 4.495025641854031E-01, h_new = 1.746438011362627E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 118, err = 4.800675473286704E-01, h_new = 1.724580901865473E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 119, err = 5.192798908156856E-01, h_new = 1.684844870093419E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 120, err = 5.610233910470050E-01, h_new = 1.629640183319557E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 121, err = 6.069521654104935E-01, h_new = 1.560117160791368E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 122, err = 6.614819642548416E-01, h_new = 1.476514923263645E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 123, err = 7.020583780421905E-01, h_new = 1.388089988852410E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 124, err = 6.395304860165552E-01, h_new = 1.328980619568514E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 125, err = 6.702741907409688E-01, h_new = 1.257571489055614E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 126, err = 6.902292948802367E-01, h_new = 1.186305150973436E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 127, err = 7.182825821575747E-01, h_new = 1.112829049628937E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 128, err = 7.378581845285024E-01, h_new = 1.040800240994675E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 129, err = 7.547839822561961E-01, h_new = 9.707311247827066E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 130, err = 7.763173587108884E-01, h_new = 9.018777873004002E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 131, err = 8.254682824770661E-01, h_new = 8.301426088989558E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 132, err = 8.791147117858668E-01, h_new = 7.578364457695930E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 133, err = 8.418055290902556E-01, h_new = 6.987049455327099E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 134, err = 7.431142213472319E-01, h_new = 6.568487084473679E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 135, err = 6.181896712669877E-01, h_new = 6.339563934648536E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 136, err = 5.075969074956047E-01, h_new = 6.280707103000815E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 137, err = 4.172546513094464E-01, h_new = 6.382687875936725E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 138, err = 3.483078294621114E-01, h_new = 6.636336725379821E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 139, err = 3.010852712643283E-01, h_new = 7.022184500813025E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 140, err = 2.808674015750031E-01, h_new = 7.475101578197800E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 141, err = 2.868667017543144E-01, h_new = 7.906677730463846E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 142, err = 2.980642184672669E-01, h_new = 8.315934870017505E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 143, err = 2.990687473972181E-01, h_new = 8.754773347159456E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 144, err = 2.928980297893877E-01, h_new = 9.250739708808453E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 145, err = 2.853741410135108E-01, h_new = 9.809957903910806E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 146, err = 2.805481509945272E-01, h_new = 1.042233379264539E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 147, err = 2.805394625089158E-01, h_new = 1.106544313340334E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 148, err = 2.866652119983522E-01, h_new = 1.170515933171230E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 149, err = 2.870276167115361E-01, h_new = 1.238990032185425E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 150, err = 2.681531404857602E-01, h_new = 1.326789965786301E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 151, err = 2.797067298974354E-01, h_new = 1.406826393841818E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 152, err = 2.913677346893253E-01, h_new = 1.483870790397390E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 153, err = 3.079613533561383E-01, h_new = 1.553001575680898E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 154, err = 3.223789688982560E-01, h_new = 1.616337002478366E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 155, err = 3.348488377520902E-01, h_new = 1.674498633570139E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 156, err = 3.466602484426959E-01, h_new = 1.727179884169247E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 157, err = 3.580837691970113E-01, h_new = 1.774184800533004E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 158, err = 3.849741152991009E-01, h_new = 1.802508781388363E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 159, err = 4.355693118006662E-01, h_new = 1.798446086953359E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 160, err = 5.023688043195735E-01, h_new = 1.760063638759287E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 161, err = 5.674041009711825E-01, h_new = 1.696875842580092E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 162, err = 6.275729214637876E-01, h_new = 1.616014903887843E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 163, err = 6.915375823599209E-01, h_new = 1.519937588087116E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 164, err = 6.892316826976499E-01, h_new = 1.435948309126776E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 165, err = 6.867369911173770E-01, h_new = 1.357255312759619E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 166, err = 7.170377744508504E-01, h_new = 1.273308186705722E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 167, err = 7.540841686062617E-01, h_new = 1.186414244340852E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 168, err = 7.982647916847645E-01, h_new = 1.097010208318843E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 169, err = 8.429340990295561E-01, h_new = 1.007289116506638E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 170, err = 8.793984872403370E-01, h_new = 9.202733052912539E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 171, err = 8.904399702437803E-01, h_new = 8.404153431859546E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 172, err = 8.675898143142085E-01, h_new = 7.712713733046735E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 173, err = 8.376857729061897E-01, h_new = 7.113093309192865E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 174, err = 8.306879764040176E-01, h_new = 6.560241295295702E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 175, err = 8.296491094722633E-01, h_new = 6.049615647598559E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 176, err = 7.401106964358213E-01, h_new = 5.687817263798044E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 177, err = 6.321875894827998E-01, h_new = 5.467845033567713E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 178, err = 5.239426779364365E-01, h_new = 5.392799871644553E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 179, err = 4.344886608158758E-01, h_new = 5.449690372804437E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 180, err = 3.661787487883571E-01, h_new = 5.627372221352843E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 181, err = 3.213054409690154E-01, h_new = 5.900921044448668E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 182, err = 3.029053112884786E-01, h_new = 6.217515338126513E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 183, err = 3.057263615729944E-01, h_new = 6.525368758773174E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 184, err = 3.140701336558686E-01, h_new = 6.819717041695036E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 185, err = 3.152411735724601E-01, h_new = 7.130510628866499E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 186, err = 3.125975598734951E-01, h_new = 7.467260620561361E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 187, err = 3.152321842637002E-01, h_new = 7.806134830487207E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 188, err = 3.488908379364377E-01, h_new = 8.023549823559063E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 189, err = 5.062804987145757E-01, h_new = 7.772668433096575E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 190, err = 9.592414745094958E-01, h_new = 6.855838020796694E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 191, err = 1.807186460141171E+00, h_new = 5.579721900209573E-02 +step(A) = 192, err = 5.340526023509135E-01, h_new = 5.577542587476984E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 193, err = 2.167530620096461E+00, h_new = 4.401202119643443E-02 +step(A) = 194, err = 5.287816011810852E-01, h_new = 4.304871647979079E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 195, err = 2.025260824778042E+00, h_new = 3.436378183295545E-02 +step(A) = 196, err = 5.289129234440509E-01, h_new = 3.359689991687961E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 197, err = 1.989732482689969E+00, h_new = 2.689964928699729E-02 +step(A) = 198, err = 5.332864252191875E-01, h_new = 2.626281076839034E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 199, err = 1.928482660204861E+00, h_new = 2.113961328789445E-02 +step(A) = 200, err = 5.362725593442063E-01, h_new = 2.062635182335257E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 201, err = 1.865738428635650E+00, h_new = 1.669630240131359E-02 +step(A) = 202, err = 5.393832629986595E-01, h_new = 1.627854802793259E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 203, err = 1.797474072634026E+00, h_new = 1.326067180870038E-02 +step(A) = 204, err = 5.424828782426873E-01, h_new = 1.291927993931127E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 205, err = 1.730073591173358E+00, h_new = 1.059277725784680E-02 +step(A) = 206, err = 5.454617690202925E-01, h_new = 1.031282996257347E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 207, err = 1.664923798899046E+00, h_new = 8.511053592694497E-03 +step(A) = 208, err = 5.482938020381437E-01, h_new = 8.280644498694249E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 209, err = 1.602073750584690E+00, h_new = 6.878767389627737E-03 +step(A) = 210, err = 5.509805552049187E-01, h_new = 6.688373235754933E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 211, err = 1.541158537617598E+00, h_new = 5.592795838435351E-03 +step(A) = 212, err = 5.535289757756192E-01, h_new = 5.434793722575373E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 213, err = 1.481755833629917E+00, h_new = 4.575025770894886E-03 +step(A) = 214, err = 5.559385798308932E-01, h_new = 4.443315077398653E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 215, err = 1.423596450050294E+00, h_new = 3.765943910507906E-03 +step(A) = 216, err = 5.581900271326309E-01, h_new = 3.655648890887928E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 217, err = 1.366767815180639E+00, h_new = 3.119887111743048E-03 +step(A) = 218, err = 5.602305988282061E-01, h_new = 3.027124702121574E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 219, err = 1.311974808773229E+00, h_new = 2.601509925103399E-03 +step(A) = 220, err = 5.619561725237517E-01, h_new = 2.523209166880239E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 221, err = 1.260773847375036E+00, h_new = 2.183169426012133E-03 +step(A) = 222, err = 5.632011165317145E-01, h_new = 2.116923927393091E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 223, err = 1.215277953433804E+00, h_new = 1.843117004348746E-03 +step(A) = 224, err = 5.637755042601063E-01, h_new = 1.787038467876647E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 225, err = 1.175475076630793E+00, h_new = 1.564732591135811E-03 +step(A) = 226, err = 5.637942230420033E-01, h_new = 1.517177454006904E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 227, err = 1.130987496530358E+00, h_new = 1.337183666367012E-03 +step(A) = 228, err = 5.636537656218453E-01, h_new = 1.296600805892454E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 229, err = 1.078062374553468E+00, h_new = 1.152124297021162E-03 +step(A) = 230, err = 5.637827543658609E-01, h_new = 1.117103307490210E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 231, err = 1.008062079186352E+00, h_new = 1.004021493082412E-03 +step(A) = 232, err = 5.641344706332592E-01, h_new = 9.734080699415192E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 233, err = 9.272491170234880E-01, h_new = 8.673000361665458E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 234, err = 9.417029763814300E-01, h_new = 7.862023918916765E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 235, err = 9.860534181238052E-01, h_new = 7.075715339126294E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 236, err = 9.598396100935189E-01, h_new = 6.409071345978661E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 237, err = 8.850310983935529E-01, h_new = 5.879530018460564E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 238, err = 1.820001853220369E-01, h_new = 5.258587607119909E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 1430 +Number of performed steps = 238 +Number of accepted steps = 217 +Number of rejected steps = 21 +y = 9.940021704030663E-01 9.040891036151961E-06 1.459758305600828E-03 -2.001245515834718E+00 +h = 5.258587607119909E-04 diff --git a/russell_ode/data/fortran_dopri5_hairer_wanner_eq1.txt b/russell_ode/data/fortran_dopri5_hairer_wanner_eq1.txt new file mode 100644 index 00000000..bd149387 --- /dev/null +++ b/russell_ode/data/fortran_dopri5_hairer_wanner_eq1.txt @@ -0,0 +1,24 @@ + +running dopri5.f test +step = 0, x = 0.00, y = 0.000000000000000E+00 +step = 10, x = 0.10, y = 9.898623749945069E-01 +step = 14, x = 0.20, y = 9.835855727509085E-01 +step = 16, x = 0.30, y = 9.608730705306801E-01 +step = 18, x = 0.40, y = 9.284913897784541E-01 +step = 20, x = 0.50, y = 8.868301683933252E-01 +step = 22, x = 0.60, y = 8.363069895336177E-01 +step = 24, x = 0.70, y = 7.774254044396482E-01 +step = 26, x = 0.80, y = 7.107729344781961E-01 +step = 28, x = 0.90, y = 6.370146442172697E-01 +step = 30, x = 1.00, y = 5.568866965219718E-01 +step = 32, x = 1.10, y = 4.711902738442056E-01 +step = 33, x = 1.20, y = 3.807874366083619E-01 +step = 35, x = 1.30, y = 2.866070362993788E-01 +step = 37, x = 1.40, y = 1.895941168684984E-01 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 236 +Number of performed steps = 39 +Number of accepted steps = 39 +Number of rejected steps = 0 +y = 9.063921649310544E-02 +h = 5.809633059575138E-02 diff --git a/russell_ode/data/fortran_dopri5_robertson.txt b/russell_ode/data/fortran_dopri5_robertson.txt new file mode 100644 index 00000000..b3d1b50c --- /dev/null +++ b/russell_ode/data/fortran_dopri5_robertson.txt @@ -0,0 +1,220 @@ + +running dopri5.f test +step = 0, x = 0.00, y = 1.000000000000000E+00 0.000000000000000E+00 0.000000000000000E+00 +step = 1, x = 0.00, y = 9.999999600000008E-01 3.999998320000817E-08 1.599999184000402E-14 +step = 2, x = 0.00, y = 9.999995600000968E-01 4.399786084436343E-07 2.129475617369957E-11 +step = 3, x = 0.00, y = 9.999955600098782E-01 4.418236742772529E-06 2.175337903903183E-08 +step = 4, x = 0.00, y = 9.999767456846284E-01 2.054873906679896E-05 2.705576304823573E-06 +step = 5, x = 0.00, y = 9.999524065410368E-01 3.150278153038586E-05 1.609067743275600E-05 +step = 6, x = 0.00, y = 9.999232470330851E-01 3.543754705031356E-05 4.131541986451330E-05 +step = 7, x = 0.00, y = 9.998897527446059E-01 3.631428719674555E-05 7.393296819731114E-05 +step = 8, x = 0.00, y = 9.998450659828790E-01 3.645309781430661E-05 1.184809193066630E-04 +step = 9, x = 0.01, y = 9.997857040267886E-01 3.644485762380736E-05 1.778511155876718E-04 +step = 10, x = 0.01, y = 9.997088296443079E-01 3.626683153964433E-05 2.549035241525512E-04 +step = 11, x = 0.01, y = 9.996468169345338E-01 3.616544155587439E-05 3.170176239103529E-04 +step = 12, x = 0.01, y = 9.995855124677069E-01 3.602241559077125E-05 3.784651167024343E-04 +step = 13, x = 0.01, y = 9.995275296648422E-01 3.598736239651928E-05 4.364829727614197E-04 +step = 14, x = 0.01, y = 9.994726881734725E-01 3.610762726748340E-05 4.912041992601934E-04 +step = 15, x = 0.01, y = 9.994177884096247E-01 3.620200952464154E-05 5.460095808508388E-04 +step = 16, x = 0.02, y = 9.993596707164227E-01 3.620445278775705E-05 6.041248307897313E-04 +step = 17, x = 0.02, y = 9.992982338757792E-01 3.611687410501441E-05 6.656492501159886E-04 +step = 18, x = 0.02, y = 9.992366407859949E-01 3.596186309862819E-05 7.273973509065962E-04 +step = 19, x = 0.02, y = 9.991783604581322E-01 3.589796939746057E-05 7.857415724704315E-04 +step = 20, x = 0.02, y = 9.991237095982611E-01 3.602141403627471E-05 8.402689877026988E-04 +step = 21, x = 0.02, y = 9.990695129080451E-01 3.613516960431551E-05 8.943519223506349E-04 +step = 22, x = 0.02, y = 9.990121379727385E-01 3.615043636198613E-05 9.517115908995828E-04 +step = 23, x = 0.03, y = 9.989509612084604E-01 3.607083492079624E-05 1.012967956618885E-03 +step = 24, x = 0.03, y = 9.988890632115257E-01 3.590694939460904E-05 1.075029839079754E-03 +step = 25, x = 0.03, y = 9.988303945899557E-01 3.580989186379223E-05 1.133795518180564E-03 +step = 26, x = 0.03, y = 9.987758356422740E-01 3.593104122825736E-05 1.188233316497829E-03 +step = 27, x = 0.03, y = 9.987223225313213E-01 3.606615970658697E-05 1.241611308972170E-03 +step = 28, x = 0.03, y = 9.986657655061216E-01 3.609571271904433E-05 1.298138781159428E-03 +step = 29, x = 0.04, y = 9.986049489503434E-01 3.602588084821912E-05 1.359025168808539E-03 +step = 30, x = 0.04, y = 9.985427691074369E-01 3.585729041157973E-05 1.421373602151591E-03 +step = 31, x = 0.04, y = 9.984836300802851E-01 3.572416462507938E-05 1.480645755089950E-03 +step = 32, x = 0.04, y = 9.984290529464225E-01 3.583565240062414E-05 1.535111401177010E-03 +step = 33, x = 0.04, y = 9.983761877816723E-01 3.599425172932388E-05 1.587817966598509E-03 +step = 34, x = 0.04, y = 9.983205210597629E-01 3.603989348544007E-05 1.643439046751815E-03 +step = 35, x = 0.04, y = 9.982601808210434E-01 3.598138273095367E-05 1.703837796225801E-03 +step = 36, x = 0.05, y = 9.981977655437617E-01 3.581270027630977E-05 1.766421755962139E-03 +step = 37, x = 0.05, y = 9.981380818057181E-01 3.564257507991768E-05 1.826275619202148E-03 +step = 38, x = 0.05, y = 9.980833632750575E-01 3.573476251246210E-05 1.880901962430118E-03 +step = 39, x = 0.05, y = 9.980310891557052E-01 3.591869954465474E-05 1.932992144750282E-03 +step = 40, x = 0.05, y = 9.979763758003092E-01 3.598266346006582E-05 1.987641536230835E-03 +step = 41, x = 0.05, y = 9.979166394905993E-01 3.593676804222570E-05 2.047423741358542E-03 +step = 42, x = 0.05, y = 9.978540603586120E-01 3.577268882247243E-05 2.110166952565604E-03 +step = 43, x = 0.06, y = 9.977937701236000E-01 3.556718676090736E-05 2.170662689639153E-03 +step = 44, x = 0.06, y = 9.977387760648037E-01 3.562827752358542E-05 2.225595657672884E-03 +step = 45, x = 0.06, y = 9.976870123093106E-01 3.583862057393137E-05 2.277149070115597E-03 +step = 46, x = 0.06, y = 9.976333003588078E-01 3.592370235803325E-05 2.330775938834186E-03 +step = 47, x = 0.06, y = 9.975743025554227E-01 3.589149583235904E-05 2.389805948744990E-03 +step = 48, x = 0.06, y = 9.975116583659726E-01 3.573641635810296E-05 2.452605217669379E-03 +step = 49, x = 0.06, y = 9.974507186297914E-01 3.550004936006744E-05 2.513781320848560E-03 +step = 50, x = 0.07, y = 9.973953078833825E-01 3.551677613649846E-05 2.569175340481060E-03 +step = 51, x = 0.07, y = 9.973439483287758E-01 3.575296297362508E-05 2.620298708250597E-03 +step = 52, x = 0.07, y = 9.972912643590470E-01 3.586264446920907E-05 2.672872996483775E-03 +step = 53, x = 0.07, y = 9.972331410087697E-01 3.584506951958990E-05 2.731013921710733E-03 +step = 54, x = 0.07, y = 9.971705591209902E-01 3.570275407376570E-05 2.793738124936041E-03 +step = 55, x = 0.07, y = 9.971089530484245E-01 3.544286609974391E-05 2.855604085475740E-03 +step = 56, x = 0.07, y = 9.970529828538903E-01 3.540190947099653E-05 2.911615236638702E-03 +step = 57, x = 0.08, y = 9.970018850358552E-01 3.566008328223138E-05 2.962454880862659E-03 +step = 58, x = 0.08, y = 9.969502310753264E-01 3.579886940034030E-05 3.013970055273340E-03 +step = 59, x = 0.08, y = 9.968931246149383E-01 3.579709142593802E-05 3.071078293635937E-03 +step = 60, x = 0.08, y = 9.968307693033621E-01 3.567074846522543E-05 3.133559948172775E-03 +step = 61, x = 0.08, y = 9.967685106326952E-01 3.539728237773513E-05 3.196092084927122E-03 +step = 62, x = 0.08, y = 9.967118314285525E-01 3.528675764206876E-05 3.252881813805498E-03 +step = 63, x = 0.08, y = 9.966608202037924E-01 3.555810904175481E-05 3.303621687165857E-03 +step = 64, x = 0.09, y = 9.966101642110686E-01 3.573160313980493E-05 3.354104185791664E-03 +step = 65, x = 0.09, y = 9.965542139600752E-01 3.574719051117208E-05 3.410038849413741E-03 +step = 66, x = 0.09, y = 9.964922793496079E-01 3.563908267378487E-05 3.472081567718367E-03 +step = 67, x = 0.09, y = 9.964294238666042E-01 3.536348961369448E-05 3.535212643782179E-03 +step = 68, x = 0.09, y = 9.963718950966219E-01 3.517624487435891E-05 3.592928658503755E-03 +step = 69, x = 0.09, y = 9.963207701346511E-01 3.544535644530235E-05 3.643784508903634E-03 +step = 70, x = 0.09, y = 9.962710360452792E-01 3.565995084799483E-05 3.693304003872908E-03 +step = 71, x = 0.10, y = 9.962163615864394E-01 3.569503242429138E-05 3.747943381136406E-03 +step = 72, x = 0.10, y = 9.961550672653940E-01 3.560650257426540E-05 3.809326232031822E-03 +step = 73, x = 0.10, y = 9.960917191341748E-01 3.534029625262029E-05 3.872940569572661E-03 +step = 74, x = 0.10, y = 9.960393213223385E-01 3.549145205791733E-05 3.925187225603691E-03 +step = 75, x = 0.10, y = 9.959869518062422E-01 3.560144267167911E-05 3.977446751086239E-03 +step = 76, x = 0.10, y = 9.959303710444377E-01 3.559812483750267E-05 4.034030830724905E-03 +step = 77, x = 0.11, y = 9.958697906613667E-01 3.548855721496400E-05 4.094720781418475E-03 +step = 78, x = 0.11, y = 9.958093686507906E-01 3.528549605046662E-05 4.155345853159118E-03 +step = 79, x = 0.11, y = 9.957532775585237E-01 3.521731200546894E-05 4.211505129470967E-03 +step = 80, x = 0.11, y = 9.957015302791822E-01 3.540245859207126E-05 4.263067262225916E-03 +step = 81, x = 0.11, y = 9.956499761772616E-01 3.553708900800212E-05 4.314486733730623E-03 +step = 82, x = 0.11, y = 9.955942703083942E-01 3.554826725629927E-05 4.370181424349724E-03 +step = 83, x = 0.11, y = 9.955339795267421E-01 3.545068985316295E-05 4.430569783405028E-03 +step = 84, x = 0.12, y = 9.954731275045743E-01 3.523936918841465E-05 4.491633126237597E-03 +step = 85, x = 0.12, y = 9.954164816619410E-01 3.512288861529776E-05 4.548395449444079E-03 +step = 86, x = 0.12, y = 9.953647615571480E-01 3.530742225366882E-05 4.599931020598615E-03 +step = 87, x = 0.12, y = 9.953139786722672E-01 3.546972931456648E-05 4.650551598418523E-03 +step = 88, x = 0.12, y = 9.952592402494369E-01 3.549697981384133E-05 4.705262770749581E-03 +step = 89, x = 0.12, y = 9.951993799108404E-01 3.541316272099974E-05 4.765206926438887E-03 +step = 90, x = 0.12, y = 9.951381479521985E-01 3.520047018006698E-05 4.826651577621764E-03 +step = 91, x = 0.13, y = 9.950808536919939E-01 3.503285235046840E-05 4.884113455655860E-03 +step = 92, x = 0.13, y = 9.950290147248372E-01 3.520525531161092E-05 4.935780019851532E-03 +step = 93, x = 0.13, y = 9.949789400992198E-01 3.539865292172178E-05 4.985661247858748E-03 +step = 94, x = 0.13, y = 9.949252488913218E-01 3.544398923833282E-05 5.039307119440120E-03 +step = 95, x = 0.13, y = 9.948659738633499E-01 3.537521344304393E-05 5.098650923207439E-03 +step = 96, x = 0.13, y = 9.948044427080823E-01 3.516808467900306E-05 5.160389207239059E-03 +step = 97, x = 0.13, y = 9.947464208727073E-01 3.495042304641202E-05 5.218628704246738E-03 +step = 98, x = 0.14, y = 9.946943035000239E-01 3.509544043021398E-05 5.270601059546320E-03 +step = 99, x = 0.14, y = 9.946448450610503E-01 3.532295116347585E-05 5.319831987786560E-03 +step = 100, x = 0.14, y = 9.945922630115911E-01 3.538903638121173E-05 5.372347952028064E-03 +step = 101, x = 0.14, y = 9.945337357004863E-01 3.533615172987055E-05 5.430928147784162E-03 +step = 102, x = 0.14, y = 9.944720186186206E-01 3.514088270705738E-05 5.492840498672751E-03 +step = 103, x = 0.14, y = 9.944132120473453E-01 3.487880522954445E-05 5.551909147425513E-03 +step = 104, x = 0.15, y = 9.943606487986771E-01 3.497816880766110E-05 5.604373032515615E-03 +step = 105, x = 0.15, y = 9.943116847548472E-01 3.524151421333193E-05 5.653073730939908E-03 +step = 106, x = 0.15, y = 9.942602487142803E-01 3.533183955591888E-05 5.704419446164214E-03 +step = 107, x = 0.15, y = 9.942026307212239E-01 3.529538694921954E-05 5.762073891827323E-03 +step = 108, x = 0.15, y = 9.941408739180639E-01 3.511706214598645E-05 5.824009019790605E-03 +step = 109, x = 0.15, y = 9.940812563165254E-01 3.482058632086716E-05 5.883923097154175E-03 +step = 110, x = 0.15, y = 9.940280795870933E-01 3.485494473848015E-05 5.937065468168695E-03 +step = 111, x = 0.16, y = 9.939794589113629E-01 3.515303867895427E-05 5.985388049958588E-03 +step = 112, x = 0.16, y = 9.939291720617283E-01 3.527204384121047E-05 6.035555894430882E-03 +step = 113, x = 0.16, y = 9.938726148050778E-01 3.525244142199358E-05 6.092132753500587E-03 +step = 114, x = 0.16, y = 9.938109962889912E-01 3.509460657305024E-05 6.153909104436155E-03 +step = 115, x = 0.16, y = 9.937505819436896E-01 3.477716511933332E-05 6.214640891191415E-03 +step = 116, x = 0.16, y = 9.936966333930957E-01 3.472928246613550E-05 6.268637324438596E-03 +step = 117, x = 0.16, y = 9.936481659686349E-01 3.505559021851642E-05 6.316778441146902E-03 +step = 118, x = 0.17, y = 9.935989935906218E-01 3.520898919723560E-05 6.365797420181344E-03 +step = 119, x = 0.17, y = 9.935436423240611E-01 3.520699815586820E-05 6.421150677783330E-03 +step = 120, x = 0.17, y = 9.934823779526777E-01 3.507196714352131E-05 6.482550080179109E-03 +step = 121, x = 0.17, y = 9.934212266743204E-01 3.474916163930484E-05 6.544024164040593E-03 +step = 122, x = 0.17, y = 9.933722385885349E-01 3.501982099009677E-05 6.592741590475352E-03 +step = 123, x = 0.17, y = 9.933232777095082E-01 3.516786815322597E-05 6.641554422338861E-03 +step = 124, x = 0.17, y = 9.932676964870939E-01 3.516011910274914E-05 6.697143393803699E-03 +step = 125, x = 0.18, y = 9.932060970328589E-01 3.501307920116133E-05 6.758889887940254E-03 +step = 126, x = 0.18, y = 9.931449879260213E-01 3.466284745614723E-05 6.820349226522756E-03 +step = 127, x = 0.18, y = 9.930967615774607E-01 3.497444630059060E-05 6.868263976238881E-03 +step = 128, x = 0.18, y = 9.930485627078026E-01 3.513064809261551E-05 6.916306644105000E-03 +step = 129, x = 0.18, y = 9.929929627166688E-01 3.512128107650050E-05 6.971916002254972E-03 +step = 130, x = 0.18, y = 9.929307235194662E-01 3.496409368074043E-05 7.034312386853291E-03 +step = 131, x = 0.18, y = 9.928691051910560E-01 3.455908763103702E-05 7.096335721313257E-03 +step = 132, x = 0.19, y = 9.928219509876504E-01 3.493387688112425E-05 7.143115135468775E-03 +step = 133, x = 0.19, y = 9.927748249881387E-01 3.509849071459688E-05 7.190076521146927E-03 +step = 134, x = 0.19, y = 9.927190553319936E-01 3.508622993130049E-05 7.245858438075395E-03 +step = 135, x = 0.19, y = 9.926557194044610E-01 3.491375246615122E-05 7.309366843073158E-03 +step = 136, x = 0.19, y = 9.925998859172012E-01 3.485544023115724E-05 7.365258642567893E-03 +step = 137, x = 0.19, y = 9.925451387686994E-01 3.482568409475036E-05 7.420035547205990E-03 +step = 138, x = 0.19, y = 9.924916093191400E-01 3.484441980618173E-05 7.473546261053987E-03 +step = 139, x = 0.20, y = 9.924384533645507E-01 3.487879386515195E-05 7.526667841584320E-03 +step = 140, x = 0.20, y = 9.923844786483885E-01 3.488673717602061E-05 7.580634614435643E-03 +step = 141, x = 0.20, y = 9.923292962004538E-01 3.485581635217449E-05 7.635847983194157E-03 +step = 142, x = 0.20, y = 9.922737065524962E-01 3.480052059611610E-05 7.691492926907809E-03 +step = 143, x = 0.20, y = 9.922189592952060E-01 3.476379786171790E-05 7.746276906932441E-03 +step = 144, x = 0.20, y = 9.921655362860383E-01 3.477929372948430E-05 7.799684420232289E-03 +step = 145, x = 0.21, y = 9.921126652353439E-01 3.481772619786434E-05 7.852517038458301E-03 +step = 146, x = 0.21, y = 9.920590473503917E-01 3.483150953498505E-05 7.906121140073363E-03 +step = 147, x = 0.21, y = 9.920041161715911E-01 3.480410534246346E-05 7.961079723066481E-03 +step = 148, x = 0.21, y = 9.919485933675829E-01 3.474701325792500E-05 8.016659619159204E-03 +step = 149, x = 0.21, y = 9.918938264534376E-01 3.470286357211094E-05 8.071470682990306E-03 +step = 150, x = 0.21, y = 9.918404818929170E-01 3.471386501332899E-05 8.124804242069759E-03 +step = 151, x = 0.21, y = 9.917878805992403E-01 3.475596635664261E-05 8.177363434403170E-03 +step = 152, x = 0.22, y = 9.917346308708023E-01 3.477597856819959E-05 8.230593150629639E-03 +step = 153, x = 0.22, y = 9.916799776119949E-01 3.475277088415048E-05 8.285269617121090E-03 +step = 154, x = 0.22, y = 9.916245373224253E-01 3.469472644884107E-05 8.340767951126000E-03 +step = 155, x = 0.22, y = 9.915697388273240E-01 3.464306504507004E-05 8.395618107631050E-03 +step = 156, x = 0.22, y = 9.915164435932471E-01 3.464823680203185E-05 8.448908169950992E-03 +step = 157, x = 0.22, y = 9.914640928880929E-01 3.469344470530328E-05 8.501213667201935E-03 +step = 158, x = 0.22, y = 9.914112196439854E-01 3.472003967329617E-05 8.554060316341506E-03 +step = 159, x = 0.23, y = 9.913568718894150E-01 3.470169099534368E-05 8.608426419589798E-03 +step = 160, x = 0.23, y = 9.913015339356319E-01 3.464361329710455E-05 8.663822451071147E-03 +step = 161, x = 0.23, y = 9.912466952581981E-01 3.458458732855465E-05 8.718720154473546E-03 +step = 162, x = 0.23, y = 9.911934195208377E-01 3.458255092543074E-05 8.771997928237057E-03 +step = 163, x = 0.23, y = 9.911412956576814E-01 3.463008066901429E-05 8.824074261649854E-03 +step = 164, x = 0.23, y = 9.910888038124461E-01 3.466357332596254E-05 8.876532614228154E-03 +step = 165, x = 0.23, y = 9.910347900493510E-01 3.465074062976922E-05 8.930559210019475E-03 +step = 166, x = 0.24, y = 9.909795788113048E-01 3.459361473405337E-05 8.985827573961372E-03 +step = 167, x = 0.24, y = 9.909246951353204E-01 3.452762324986749E-05 9.040777241429973E-03 +step = 168, x = 0.24, y = 9.908714083977792E-01 3.451698944393096E-05 9.094074612777199E-03 +step = 169, x = 0.24, y = 9.908194820392900E-01 3.456575881417862E-05 9.145952201896127E-03 +step = 170, x = 0.24, y = 9.907673728792644E-01 3.460642589759598E-05 9.198020694838377E-03 +step = 171, x = 0.24, y = 9.907137231169023E-01 3.459979551962777E-05 9.251677087578415E-03 +step = 172, x = 0.24, y = 9.906586684130295E-01 3.454468266529146E-05 9.306786904305555E-03 +step = 173, x = 0.25, y = 9.906037390078338E-01 3.447239149808527E-05 9.361788600668474E-03 +step = 174, x = 0.25, y = 9.905504094851546E-01 3.445177746662894E-05 9.415138737379126E-03 +step = 175, x = 0.25, y = 9.904986471941342E-01 3.450042708466307E-05 9.466852378781587E-03 +step = 176, x = 0.25, y = 9.904469171293200E-01 3.454846806649680E-05 9.518534402613981E-03 +step = 177, x = 0.25, y = 9.903936606520903E-01 3.454871618300398E-05 9.571790631727115E-03 +step = 178, x = 0.25, y = 9.903387965909344E-01 3.449668234608097E-05 9.626706726719945E-03 +step = 179, x = 0.26, y = 9.902838260545459E-01 3.441902267319150E-05 9.681754922781342E-03 +step = 180, x = 0.26, y = 9.902304230731229E-01 3.438720153781506E-05 9.735189725339708E-03 +step = 181, x = 0.26, y = 9.901787873165800E-01 3.443406124238479E-05 9.786778622178052E-03 +step = 182, x = 0.26, y = 9.901274272808512E-01 3.448956737937893E-05 9.838083151769915E-03 +step = 183, x = 0.26, y = 9.900745916732023E-01 3.449736209943361E-05 9.890910964698820E-03 +step = 184, x = 0.26, y = 9.900199544278533E-01 3.444938791913851E-05 9.945596184228205E-03 +step = 185, x = 0.26, y = 9.899649539113183E-01 3.436752504260843E-05 1.000067856363965E-02 +step = 186, x = 0.27, y = 9.899114509497128E-01 3.432360347250950E-05 1.005422544681526E-02 +step = 187, x = 0.27, y = 9.898599020513472E-01 3.436675402368062E-05 1.010573119462965E-02 +step = 188, x = 0.27, y = 9.898088964609869E-01 3.442964189133031E-05 1.015667389712229E-02 +step = 189, x = 0.27, y = 9.897565045268731E-01 3.444558827095846E-05 1.020904988485644E-02 +step = 190, x = 0.27, y = 9.897021336939672E-01 3.440262091313446E-05 1.026346368512024E-02 +step = 191, x = 0.27, y = 9.896471205134638E-01 3.431790651776149E-05 1.031856158001902E-02 +step = 192, x = 0.27, y = 9.895934942561309E-01 3.426133380797698E-05 1.037224441006179E-02 +step = 193, x = 0.28, y = 9.895419898347375E-01 3.429857769872335E-05 1.042371158756442E-02 +step = 194, x = 0.28, y = 9.894913170052027E-01 3.436856452785918E-05 1.047431443027015E-02 +step = 195, x = 0.28, y = 9.894393877569793E-01 3.439326297774563E-05 1.052621898004367E-02 +step = 196, x = 0.28, y = 9.893853251594878E-01 3.435619182019548E-05 1.058031864869266E-02 +step = 197, x = 0.28, y = 9.893303230637335E-01 3.427011060246495E-05 1.063540682566474E-02 +step = 198, x = 0.28, y = 9.892765544212725E-01 3.420075656293940E-05 1.068924482216527E-02 +step = 199, x = 0.28, y = 9.892250501651890E-01 3.422966657007450E-05 1.074072016824165E-02 +step = 200, x = 0.29, y = 9.891746821307823E-01 3.430620844880151E-05 1.079101166076969E-02 +step = 201, x = 0.29, y = 9.891232298041785E-01 3.434025535987359E-05 1.084242994046243E-02 +step = 202, x = 0.29, y = 9.890695185676761E-01 3.430990629828581E-05 1.089617152602645E-02 +step = 203, x = 0.29, y = 9.890145578528799E-01 3.422401536164006E-05 1.095121813175922E-02 +step = 204, x = 0.29, y = 9.889606330030102E-01 3.414222772350323E-05 1.100522476926715E-02 +step = 205, x = 0.29, y = 9.889090836024402E-01 3.416023122398755E-05 1.105675616633666E-02 +step = 206, x = 0.29, y = 9.888589860952506E-01 3.424245104221450E-05 1.110677145370806E-02 +step = 207, x = 0.30, y = 9.888080191751936E-01 3.428643395818919E-05 1.115769439084912E-02 +step = 208, x = 0.30, y = 9.887547026563621E-01 3.426357121263429E-05 1.121103377242617E-02 +step = 209, x = 0.30, y = 9.886998200902153E-01 3.417943744871156E-05 1.126600047233694E-02 +step = 210, x = 0.30, y = 9.886740219082688E-01 3.441229862210223E-05 1.129156579311001E-02 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 1298 +Number of performed steps = 216 +Number of accepted steps = 210 +Number of rejected steps = 6 +y = 9.886740219082688E-01 3.441229862210223E-05 1.129156579311001E-02 +h = 1.473422640837133E-03 diff --git a/russell_ode/data/fortran_dopri5_van_der_pol_debug.txt b/russell_ode/data/fortran_dopri5_van_der_pol_debug.txt new file mode 100644 index 00000000..7e509656 --- /dev/null +++ b/russell_ode/data/fortran_dopri5_van_der_pol_debug.txt @@ -0,0 +1,438 @@ + +running dopri5.f test +step(A) = 1, err = 3.727760587956869E-06, h_new = 5.213103498972100E-04, n_yes = 0, n_no = 1, h*lambda = 9.997470703048382E-02 +step(A) = 2, err = 1.240953492473138E-02, h_new = 6.845425066468906E-04, n_yes = 0, n_no = 2, h*lambda = 5.212932725594240E-01 +step(A) = 3, err = 2.688194416514796E-02, h_new = 9.558391963414619E-04, n_yes = 0, n_no = 3, h*lambda = 6.844363978402807E-01 +step(A) = 4, err = 7.358690218532347E-02, h_new = 1.159977816134844E-03, n_yes = 0, n_no = 4, h*lambda = 9.551832656577894E-01 +step(A) = 5, err = 7.734184510533144E-02, h_new = 1.453227956747536E-03, n_yes = 0, n_no = 5, h*lambda = 1.158113848815346E+00 +step(A) = 6, err = 8.155247362351542E-02, h_new = 1.807875507561472E-03, n_yes = 0, n_no = 6, h*lambda = 1.449058825392334E+00 +step(A) = 7, err = 6.439628755218164E-02, h_new = 2.346184573138853E-03, n_yes = 0, n_no = 7, h*lambda = 1.799776360945848E+00 +step(A) = 8, err = 5.074827815250559E-02, h_new = 3.140779839550399E-03, n_yes = 0, n_no = 8, h*lambda = 2.330763902743438E+00 +step(A) = 9, err = 5.307555787744915E-02, h_new = 4.132994048947248E-03, n_yes = 0, n_no = 9, h*lambda = 3.111350910805671E+00 +step(A) = 10, err = 1.828230653840881E-01, h_new = 4.415275469091488E-03, n_yes = 1, n_no = 0, h*lambda = 4.079076160810598E+00 +step(A) = 11, err = 9.983777781302734E-01, h_new = 3.713653838225876E-03, n_yes = 2, n_no = 0, h*lambda = 4.340439785241045E+00 +step(R) = 12, err = 1.958559439663303E+00, h_new = 2.981361783324316E-03 +step(A) = 13, err = 5.429798041880982E-01, h_new = 2.976567673113339E-03, n_yes = 2, n_no = 1, h*lambda = 2.922935427793524E+00 +step(A) = 14, err = 2.604231783693373E-01, h_new = 3.286124999538735E-03, n_yes = 2, n_no = 2, h*lambda = 2.910250138596906E+00 +step(A) = 15, err = 2.185708904414213E-01, h_new = 3.629301761792994E-03, n_yes = 2, n_no = 3, h*lambda = 3.203240293388239E+00 +step(A) = 16, err = 3.190115285501510E-01, h_new = 3.732520464824205E-03, n_yes = 3, n_no = 0, h*lambda = 3.525998778095849E+00 +step(A) = 17, err = 5.522585322536945E-01, h_new = 3.550035927656333E-03, n_yes = 4, n_no = 0, h*lambda = 3.613863991483097E+00 +step(A) = 18, err = 7.044683881386191E-01, h_new = 3.311498010544673E-03, n_yes = 5, n_no = 0, h*lambda = 3.425947123552543E+00 +step(A) = 19, err = 5.762683559805580E-01, h_new = 3.227566930142037E-03, n_yes = 5, n_no = 1, h*lambda = 3.185913475885057E+00 +step(A) = 20, err = 3.923779967127298E-01, h_new = 3.331294038260041E-03, n_yes = 5, n_no = 2, h*lambda = 3.095782642831596E+00 +step(A) = 21, err = 3.150183898294197E-01, h_new = 3.514686219593283E-03, n_yes = 5, n_no = 3, h*lambda = 3.185287472114879E+00 +step(A) = 22, err = 3.399300730445723E-01, h_new = 3.628492873521982E-03, n_yes = 6, n_no = 0, h*lambda = 3.349542976403127E+00 +step(A) = 23, err = 4.377868682190470E-01, h_new = 3.599230854914461E-03, n_yes = 7, n_no = 0, h*lambda = 3.446184457471084E+00 +step(A) = 24, err = 5.314409248835892E-01, h_new = 3.489597785948117E-03, n_yes = 8, n_no = 0, h*lambda = 3.406762217146414E+00 +step(A) = 25, err = 5.258363701584914E-01, h_new = 3.415792466049110E-03, n_yes = 9, n_no = 0, h*lambda = 3.292033490403043E+00 +step(A) = 26, err = 4.464080548569233E-01, h_new = 3.436477991049893E-03, n_yes = 9, n_no = 1, h*lambda = 3.211875898655271E+00 +step(A) = 27, err = 3.833585347442178E-01, h_new = 3.524783801346556E-03, n_yes = 9, n_no = 2, h*lambda = 3.220655297222187E+00 +step(A) = 28, err = 3.745240530606989E-01, h_new = 3.607676997473170E-03, n_yes = 10, n_no = 0, h*lambda = 3.292190171398073E+00 +step(A) = 29, err = 4.125127147932751E-01, h_new = 3.628983534443945E-03, n_yes = 11, n_no = 0, h*lambda = 3.357856986287223E+00 +step(A) = 30, err = 4.629698432211843E-01, h_new = 3.593362582513521E-03, n_yes = 12, n_no = 0, h*lambda = 3.365788505507941E+00 +step(A) = 31, err = 4.807404728719064E-01, h_new = 3.551737348006160E-03, n_yes = 13, n_no = 0, h*lambda = 3.321068963245464E+00 +step(A) = 32, err = 4.552668253450887E-01, h_new = 3.548579653715641E-03, n_yes = 14, n_no = 0, h*lambda = 3.271163656930549E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 8.973510130811428E-02, ACCEPTED STEP = 32 +step(A) = 33, err = 4.187997643679062E-01, h_new = 3.588282399522332E-03, n_yes = 15, n_no = 0, h*lambda = 3.256824769569957E+00 +step(A) = 34, err = 4.023721900725468E-01, h_new = 3.641016347314295E-03, n_yes = 16, n_no = 0, h*lambda = 3.281567253670414E+00 +step(A) = 35, err = 4.128776652616866E-01, h_new = 3.672489985558470E-03, n_yes = 17, n_no = 0, h*lambda = 3.317746879037275E+00 +step(A) = 36, err = 4.372081522525559E-01, h_new = 3.672137905119492E-03, n_yes = 18, n_no = 0, h*lambda = 3.334163600768078E+00 +step(A) = 37, err = 4.534015784165334E-01, h_new = 3.657521664994581E-03, n_yes = 19, n_no = 0, h*lambda = 3.321571853459977E+00 +step(A) = 38, err = 4.491089007339857E-01, h_new = 3.654171745492282E-03, n_yes = 20, n_no = 0, h*lambda = 3.296159844234577E+00 +step(A) = 39, err = 4.322176200204242E-01, h_new = 3.673297571645196E-03, n_yes = 21, n_no = 0, h*lambda = 3.280955619906580E+00 +step(A) = 40, err = 4.191700439023628E-01, h_new = 3.706127699014764E-03, n_yes = 22, n_no = 0, h*lambda = 3.285802075575150E+00 +step(A) = 41, err = 4.190025846064984E-01, h_new = 3.734923053989653E-03, n_yes = 23, n_no = 0, h*lambda = 3.302611873943486E+00 +step(A) = 42, err = 4.290236789614592E-01, h_new = 3.748789215377386E-03, n_yes = 24, n_no = 0, h*lambda = 3.315509301368293E+00 +step(A) = 43, err = 4.392810014279703E-01, h_new = 3.751168478178395E-03, n_yes = 25, n_no = 0, h*lambda = 3.314948466779248E+00 +step(A) = 44, err = 4.412782483727336E-01, h_new = 3.754202097903182E-03, n_yes = 26, n_no = 0, h*lambda = 3.304151076523684E+00 +step(A) = 45, err = 4.349692823545975E-01, h_new = 3.767130787642624E-03, n_yes = 27, n_no = 0, h*lambda = 3.293884821666195E+00 +step(A) = 46, err = 4.271791251749550E-01, h_new = 3.789551797221630E-03, n_yes = 28, n_no = 0, h*lambda = 3.292185526777186E+00 +step(A) = 47, err = 4.242905792585751E-01, h_new = 3.813747902134294E-03, n_yes = 29, n_no = 0, h*lambda = 3.298567850297265E+00 +step(A) = 48, err = 4.274049868596863E-01, h_new = 3.832289394394829E-03, n_yes = 30, n_no = 0, h*lambda = 3.306234916786778E+00 +step(A) = 49, err = 4.327411889339119E-01, h_new = 3.843931062320482E-03, n_yes = 31, n_no = 0, h*lambda = 3.308770400607540E+00 +step(A) = 50, err = 4.354784875714566E-01, h_new = 3.853389318092278E-03, n_yes = 32, n_no = 0, h*lambda = 3.305185144258213E+00 +step(A) = 51, err = 4.338299364634911E-01, h_new = 3.866337385609927E-03, n_yes = 33, n_no = 0, h*lambda = 3.299597220826963E+00 +step(A) = 52, err = 4.299482582564692E-01, h_new = 3.884671380738773E-03, n_yes = 34, n_no = 0, h*lambda = 3.296854674172394E+00 +step(A) = 53, err = 4.273038470191889E-01, h_new = 3.905783683190740E-03, n_yes = 35, n_no = 0, h*lambda = 3.298510698645459E+00 +step(A) = 54, err = 4.276130562871120E-01, h_new = 3.925558968985641E-03, n_yes = 36, n_no = 0, h*lambda = 3.302291579858245E+00 +step(A) = 55, err = 4.299022185899039E-01, h_new = 3.941969018536166E-03, n_yes = 37, n_no = 0, h*lambda = 3.304705624712688E+00 +step(A) = 56, err = 4.318278667904522E-01, h_new = 3.956286103097857E-03, n_yes = 38, n_no = 0, h*lambda = 3.304077414865707E+00 +step(A) = 57, err = 4.318055393702508E-01, h_new = 3.971399995522599E-03, n_yes = 39, n_no = 0, h*lambda = 3.301511376599565E+00 +step(A) = 58, err = 4.301433302689515E-01, h_new = 3.989178098074373E-03, n_yes = 40, n_no = 0, h*lambda = 3.299427218684822E+00 +step(A) = 59, err = 4.283980039980445E-01, h_new = 4.009187785386613E-03, n_yes = 41, n_no = 0, h*lambda = 3.299349782509169E+00 +step(A) = 60, err = 4.278528298765437E-01, h_new = 4.029514807416680E-03, n_yes = 42, n_no = 0, h*lambda = 3.300883653773780E+00 +step(A) = 61, err = 4.285629478102378E-01, h_new = 4.048597068577122E-03, n_yes = 43, n_no = 0, h*lambda = 3.302431999138754E+00 +step(A) = 62, err = 4.295742454422359E-01, h_new = 4.066409866812659E-03, n_yes = 44, n_no = 0, h*lambda = 3.302719490357073E+00 +step(A) = 63, err = 4.298863519095621E-01, h_new = 4.084181818148554E-03, n_yes = 45, n_no = 0, h*lambda = 3.301742927630345E+00 +step(A) = 64, err = 4.292626614581955E-01, h_new = 4.103163227947270E-03, n_yes = 46, n_no = 0, h*lambda = 3.300508019483566E+00 +step(A) = 65, err = 4.282627026612512E-01, h_new = 4.123628047632168E-03, n_yes = 47, n_no = 0, h*lambda = 3.300014559491820E+00 +step(A) = 66, err = 4.276241233630349E-01, h_new = 4.144859665351713E-03, n_yes = 48, n_no = 0, h*lambda = 3.300460431134471E+00 +step(A) = 67, err = 4.276369328994878E-01, h_new = 4.165930719970470E-03, n_yes = 49, n_no = 0, h*lambda = 3.301252668087124E+00 +step(A) = 68, err = 4.280092024193676E-01, h_new = 4.186494575649766E-03, n_yes = 50, n_no = 0, h*lambda = 3.301645687404717E+00 +step(A) = 69, err = 4.282204831855134E-01, h_new = 4.206953407913545E-03, n_yes = 51, n_no = 0, h*lambda = 3.301367652405347E+00 +step(A) = 70, err = 4.279837977259472E-01, h_new = 4.227993036622881E-03, n_yes = 52, n_no = 0, h*lambda = 3.300738226216595E+00 +step(A) = 71, err = 4.274266185959482E-01, h_new = 4.249985025858271E-03, n_yes = 53, n_no = 0, h*lambda = 3.300289463936793E+00 +step(A) = 72, err = 4.269015487143270E-01, h_new = 4.272761559291803E-03, n_yes = 54, n_no = 0, h*lambda = 3.300296852305110E+00 +step(A) = 73, err = 4.266570641942520E-01, h_new = 4.295867290392442E-03, n_yes = 55, n_no = 0, h*lambda = 3.300613643146745E+00 +step(A) = 74, err = 4.266619080992640E-01, h_new = 4.318990665845511E-03, n_yes = 56, n_no = 0, h*lambda = 3.300876408473815E+00 +step(A) = 75, err = 4.266887927143362E-01, h_new = 4.342193967278273E-03, n_yes = 57, n_no = 0, h*lambda = 3.300840114993769E+00 +step(A) = 76, err = 4.265362055841218E-01, h_new = 4.365798379295038E-03, n_yes = 58, n_no = 0, h*lambda = 3.300548680184669E+00 +step(A) = 77, err = 4.261836256260697E-01, h_new = 4.390085430569889E-03, n_yes = 59, n_no = 0, h*lambda = 3.300239345765668E+00 +step(A) = 78, err = 4.257697261779472E-01, h_new = 4.415090795345218E-03, n_yes = 60, n_no = 0, h*lambda = 3.300112913642361E+00 +step(A) = 79, err = 4.254479431183302E-01, h_new = 4.440636731222893E-03, n_yes = 61, n_no = 0, h*lambda = 3.300181318849538E+00 +step(A) = 80, err = 4.252609652723980E-01, h_new = 4.466529173762782E-03, n_yes = 62, n_no = 0, h*lambda = 3.300297748188529E+00 +step(A) = 81, err = 4.251299620507933E-01, h_new = 4.492728906890641E-03, n_yes = 63, n_no = 0, h*lambda = 3.300306923355136E+00 +step(A) = 82, err = 4.249426797540556E-01, h_new = 4.519365146404788E-03, n_yes = 64, n_no = 0, h*lambda = 3.300170619890225E+00 +step(A) = 83, err = 4.246496712810864E-01, h_new = 4.546612282543069E-03, n_yes = 65, n_no = 0, h*lambda = 3.299973567617452E+00 +step(A) = 84, err = 4.242902785325018E-01, h_new = 4.574555891799050E-03, n_yes = 66, n_no = 0, h*lambda = 3.299830092028819E+00 +step(A) = 85, err = 4.239422960826399E-01, h_new = 4.603157382739926E-03, n_yes = 67, n_no = 0, h*lambda = 3.299786765752348E+00 +step(A) = 86, err = 4.236516104084949E-01, h_new = 4.632325801040992E-03, n_yes = 68, n_no = 0, h*lambda = 3.299799497146005E+00 +step(A) = 87, err = 4.234021294822136E-01, h_new = 4.662017979379640E-03, n_yes = 69, n_no = 0, h*lambda = 3.299787665247680E+00 +step(A) = 88, err = 4.231413596539532E-01, h_new = 4.692281342521445E-03, n_yes = 70, n_no = 0, h*lambda = 3.299706001124871E+00 +step(A) = 89, err = 4.228295479462742E-01, h_new = 4.723216647528324E-03, n_yes = 71, n_no = 0, h*lambda = 3.299573225257547E+00 +step(A) = 90, err = 4.224683252333351E-01, h_new = 4.754906518256444E-03, n_yes = 72, n_no = 0, h*lambda = 3.299443688642741E+00 +step(A) = 91, err = 4.220903214023303E-01, h_new = 4.787373833360153E-03, n_yes = 73, n_no = 0, h*lambda = 3.299356575427231E+00 +step(A) = 92, err = 4.217262527486801E-01, h_new = 4.820597360350105E-03, n_yes = 74, n_no = 0, h*lambda = 3.299307852672613E+00 +step(A) = 93, err = 4.213804668437543E-01, h_new = 4.854560809514677E-03, n_yes = 75, n_no = 0, h*lambda = 3.299262534479612E+00 +step(A) = 94, err = 4.210326121934276E-01, h_new = 4.889289530465294E-03, n_yes = 76, n_no = 0, h*lambda = 3.299189029321810E+00 +step(A) = 95, err = 4.206585781340944E-01, h_new = 4.924848070603226E-03, n_yes = 77, n_no = 0, h*lambda = 3.299083582428204E+00 +step(A) = 96, err = 4.202499803473896E-01, h_new = 4.961308437665509E-03, n_yes = 78, n_no = 0, h*lambda = 3.298967423756141E+00 +step(A) = 97, err = 4.198169838146367E-01, h_new = 4.998720385112455E-03, n_yes = 79, n_no = 0, h*lambda = 3.298864619069140E+00 +step(A) = 98, err = 4.193757250581133E-01, h_new = 5.037107212991481E-03, n_yes = 80, n_no = 0, h*lambda = 3.298782313284495E+00 +step(A) = 99, err = 4.189335980660140E-01, h_new = 5.076485536099562E-03, n_yes = 81, n_no = 0, h*lambda = 3.298708596717074E+00 +step(A) = 100, err = 4.184845596148351E-01, h_new = 5.116888642345174E-03, n_yes = 82, n_no = 0, h*lambda = 3.298625996384025E+00 +step(A) = 101, err = 4.180160636392798E-01, h_new = 5.158374245907839E-03, n_yes = 83, n_no = 0, h*lambda = 3.298526576377812E+00 +step(A) = 102, err = 4.175196485347982E-01, h_new = 5.201013723194422E-03, n_yes = 84, n_no = 0, h*lambda = 3.298416291142185E+00 +step(A) = 103, err = 4.169959803376240E-01, h_new = 5.244875313946208E-03, n_yes = 85, n_no = 0, h*lambda = 3.298307381257485E+00 +step(A) = 104, err = 4.164515043345261E-01, h_new = 5.290016156001509E-03, n_yes = 86, n_no = 0, h*lambda = 3.298207372746440E+00 +step(A) = 105, err = 4.158911347372369E-01, h_new = 5.336488066000710E-03, n_yes = 87, n_no = 0, h*lambda = 3.298114148690524E+00 +step(A) = 106, err = 4.153136252589897E-01, h_new = 5.384350066409061E-03, n_yes = 88, n_no = 0, h*lambda = 3.298019744859849E+00 +step(A) = 107, err = 4.147127240176202E-01, h_new = 5.433676684227517E-03, n_yes = 89, n_no = 0, h*lambda = 3.297918025572469E+00 +step(A) = 108, err = 4.140819615578625E-01, h_new = 5.484556618667384E-03, n_yes = 90, n_no = 0, h*lambda = 3.297809325718488E+00 +step(A) = 109, err = 4.134184881002320E-01, h_new = 5.537085173718872E-03, n_yes = 91, n_no = 0, h*lambda = 3.297699007140089E+00 +step(A) = 110, err = 4.127230989653087E-01, h_new = 5.591358225306473E-03, n_yes = 92, n_no = 0, h*lambda = 3.297592357070076E+00 +step(A) = 111, err = 4.119972187572797E-01, h_new = 5.647472821220519E-03, n_yes = 93, n_no = 0, h*lambda = 3.297490657401036E+00 +step(A) = 112, err = 4.112398654530366E-01, h_new = 5.705533306165729E-03, n_yes = 94, n_no = 0, h*lambda = 3.297391313375605E+00 +step(A) = 113, err = 4.104469888410856E-01, h_new = 5.765657764230638E-03, n_yes = 95, n_no = 0, h*lambda = 3.297291101569820E+00 +step(A) = 114, err = 4.096131826626377E-01, h_new = 5.827980436384252E-03, n_yes = 96, n_no = 0, h*lambda = 3.297189317787390E+00 +step(A) = 115, err = 4.087338827100014E-01, h_new = 5.892649949218874E-03, n_yes = 97, n_no = 0, h*lambda = 3.297088360977995E+00 +step(A) = 116, err = 4.078061727002866E-01, h_new = 5.959826715836608E-03, n_yes = 98, n_no = 0, h*lambda = 3.296991843789327E+00 +step(A) = 117, err = 4.068278160431442E-01, h_new = 6.029683065901680E-03, n_yes = 99, n_no = 0, h*lambda = 3.296902269471037E+00 +step(A) = 118, err = 4.057956124292814E-01, h_new = 6.102407023815352E-03, n_yes = 100, n_no = 0, h*lambda = 3.296820238846854E+00 +step(A) = 119, err = 4.047044721772358E-01, h_new = 6.178207835895364E-03, n_yes = 101, n_no = 0, h*lambda = 3.296745534749260E+00 +step(A) = 120, err = 4.035477273735152E-01, h_new = 6.257320635686081E-03, n_yes = 102, n_no = 0, h*lambda = 3.296678879229823E+00 +step(A) = 121, err = 4.023180971205103E-01, h_new = 6.340009206905153E-03, n_yes = 103, n_no = 0, h*lambda = 3.296622888215403E+00 +step(A) = 122, err = 4.010083145404920E-01, h_new = 6.426567990759728E-03, n_yes = 104, n_no = 0, h*lambda = 3.296581706300584E+00 +step(A) = 123, err = 3.996108814224650E-01, h_new = 6.517325459696554E-03, n_yes = 105, n_no = 0, h*lambda = 3.296559976829165E+00 +step(A) = 124, err = 3.981171943075251E-01, h_new = 6.612650233252473E-03, n_yes = 106, n_no = 0, h*lambda = 3.296562218466875E+00 +step(A) = 125, err = 3.965166987547910E-01, h_new = 6.712959806131000E-03, n_yes = 107, n_no = 0, h*lambda = 3.296593170406035E+00 +step(A) = 126, err = 3.947965257597619E-01, h_new = 6.818730889981018E-03, n_yes = 108, n_no = 0, h*lambda = 3.296658832778334E+00 +step(A) = 127, err = 3.929415338950972E-01, h_new = 6.930510789484362E-03, n_yes = 109, n_no = 0, h*lambda = 3.296767499179250E+00 +step(A) = 128, err = 3.909343060352350E-01, h_new = 7.048930485777037E-03, n_yes = 110, n_no = 0, h*lambda = 3.296930315357218E+00 +step(A) = 129, err = 3.887546851856738E-01, h_new = 7.174721211828735E-03, n_yes = 111, n_no = 0, h*lambda = 3.297161486201307E+00 +step(A) = 130, err = 3.863787537776220E-01, h_new = 7.308736671995058E-03, n_yes = 112, n_no = 0, h*lambda = 3.297478671472719E+00 +step(A) = 131, err = 3.837774367222587E-01, h_new = 7.451982895310504E-03, n_yes = 113, n_no = 0, h*lambda = 3.297904088915819E+00 +step(A) = 132, err = 3.809149091891968E-01, h_new = 7.605657796493046E-03, n_yes = 114, n_no = 0, h*lambda = 3.298466526325542E+00 +step(A) = 133, err = 3.777467356030886E-01, h_new = 7.771203595909962E-03, n_yes = 115, n_no = 0, h*lambda = 3.299204218927224E+00 +step(A) = 134, err = 3.742173477307187E-01, h_new = 7.950377668671118E-03, n_yes = 116, n_no = 0, h*lambda = 3.300168645487699E+00 +step(A) = 135, err = 3.702562431552448E-01, h_new = 8.145351292688528E-03, n_yes = 117, n_no = 0, h*lambda = 3.301429746766151E+00 +step(A) = 136, err = 3.657721211404258E-01, h_new = 8.358851738839223E-03, n_yes = 118, n_no = 0, h*lambda = 3.303083723809014E+00 +step(A) = 137, err = 3.606438455258906E-01, h_new = 8.594373159045208E-03, n_yes = 119, n_no = 0, h*lambda = 3.305265376009520E+00 +step(A) = 138, err = 3.547062731112082E-01, h_new = 8.856500450814979E-03, n_yes = 120, n_no = 0, h*lambda = 3.308168166822950E+00 +step(A) = 139, err = 3.477270648763295E-01, h_new = 9.151428041711622E-03, n_yes = 121, n_no = 0, h*lambda = 3.312077654455514E+00 +step(A) = 140, err = 3.393664556519749E-01, h_new = 9.487836744273925E-03, n_yes = 122, n_no = 0, h*lambda = 3.317429288356214E+00 +step(A) = 141, err = 3.291024947836493E-01, h_new = 9.878481014840141E-03, n_yes = 123, n_no = 0, h*lambda = 3.324913729029330E+00 +step(A) = 142, err = 3.160801716838454E-01, h_new = 1.034333028832478E-02, n_yes = 124, n_no = 0, h*lambda = 3.335682132106093E+00 +step(A) = 143, err = 2.987727607038674E-01, h_new = 1.091658554969621E-02, n_yes = 125, n_no = 0, h*lambda = 3.351781701060984E+00 +step(A) = 144, err = 2.741028248923998E-01, h_new = 1.166534735209562E-02, n_yes = 126, n_no = 0, h*lambda = 3.377188017194614E+00 +step(A) = 145, err = 2.346239529623908E-01, h_new = 1.275538071971833E-02, n_yes = 127, n_no = 0, h*lambda = 3.420643301256491E+00 +step(A) = 146, err = 1.557890191646284E-01, h_new = 1.486002588345571E-02, n_yes = 128, n_no = 0, h*lambda = 3.504653543083134E+00 +step(A) = 147, err = 1.226099301421332E-01, h_new = 1.773838960147024E-02, n_yes = 129, n_no = 0, h*lambda = 3.804979086279828E+00 +step(R) = 148, err = 1.865508033799077E+00, h_new = 1.435890055765922E-02 +step(A) = 149, err = 5.567258694180440E-01, h_new = 1.312640152132764E-02, n_yes = 130, n_no = 0, h*lambda = 3.336672497878781E+00 +step(A) = 150, err = 4.497492134490652E-01, h_new = 1.321931282903480E-02, n_yes = 130, n_no = 1, h*lambda = 2.776090342501430E+00 +step(A) = 151, err = 2.958470302177168E-01, h_new = 1.417388091543196E-02, n_yes = 130, n_no = 2, h*lambda = 2.510927312771701E+00 +step(A) = 152, err = 2.808093537513418E-01, h_new = 1.507800943483606E-02, n_yes = 130, n_no = 3, h*lambda = 2.346913131343200E+00 +step(A) = 153, err = 2.677710048675263E-01, h_new = 1.613627102742321E-02, n_yes = 130, n_no = 4, h*lambda = 2.080888195177723E+00 +step(A) = 154, err = 2.265592527176492E-01, h_new = 1.773271868994997E-02, n_yes = 130, n_no = 5, h*lambda = 1.724597526689825E+00 +step(A) = 155, err = 1.595875690407286E-01, h_new = 2.054543146096174E-02, n_yes = 0, n_no = 6, h*lambda = 1.234987459122405E+00 +step(R) = 156, err = 2.116701634616780E+00, h_new = 1.627779728778869E-02 +step(A) = 157, err = 3.609774241177324E-01, h_new = 1.618775044370163E-02, n_yes = 0, n_no = 7, h*lambda = 9.990658038115804E-02 +step(R) = 158, err = 9.287356029822718E+00, h_new = 9.974413167062967E-03 +step(R) = 159, err = 1.287900027183122E+00, h_new = 8.599038580060907E-03 +step(A) = 160, err = 6.026470923287880E-01, h_new = 8.098058083031089E-03, n_yes = 0, n_no = 8, h*lambda = 3.967658571562481E-01 +step(A) = 161, err = 8.494145900007243E-01, h_new = 7.343034483198105E-03, n_yes = 0, n_no = 9, h*lambda = 1.828023413537553E+00 +step(R) = 162, err = 2.666288518306080E+07, h_new = 1.468606896639621E-03 +step(A) = 163, err = 3.230695729117591E-02, h_new = 2.353599329454549E-03, n_yes = 0, n_no = 10, h*lambda = 2.238398995440441E+00 +step(A) = 164, err = 3.163001418030857E-01, h_new = 1.401209326574877E-03, n_yes = 0, n_no = 11, h*lambda = 1.436600222723829E+00 +step(A) = 165, err = 6.786979900907592E-01, h_new = 1.286365809968859E-03, n_yes = 0, n_no = 12, h*lambda = 8.443327662465148E-01 +step(A) = 166, err = 8.209750533747804E-01, h_new = 1.178794137219260E-03, n_yes = 0, n_no = 13, h*lambda = 1.192678783949197E+00 +step(R) = 167, err = 1.576228468234167E+00, h_new = 9.819406027265132E-04 +step(A) = 168, err = 5.647098916180083E-01, h_new = 9.662524518772293E-04, n_yes = 0, n_no = 14, h*lambda = 9.651650874607449E-01 +step(A) = 169, err = 6.412908858800414E-01, h_new = 9.166568172831216E-04, n_yes = 0, n_no = 15, h*lambda = 9.687036860166818E-01 +step(A) = 170, err = 5.052997920524998E-01, h_new = 9.101817736226046E-04, n_yes = 0, n_no = 16, h*lambda = 9.239742915135311E-01 +step(A) = 171, err = 4.628627891109816E-01, h_new = 9.086271786241421E-04, n_yes = 0, n_no = 17, h*lambda = 9.192354929623804E-01 +step(A) = 172, err = 3.911348116724248E-01, h_new = 9.301453586853670E-04, n_yes = 0, n_no = 18, h*lambda = 9.179383411554644E-01 +step(A) = 173, err = 1.552925699886899E-01, h_new = 1.106600281751699E-03, n_yes = 0, n_no = 19, h*lambda = 9.393384125637803E-01 +step(A) = 174, err = 1.436442279292664E-01, h_new = 1.285702008912774E-03, n_yes = 0, n_no = 20, h*lambda = 1.116711101707389E+00 +step(A) = 175, err = 1.031704203432106E-01, h_new = 1.575324310472909E-03, n_yes = 0, n_no = 21, h*lambda = 1.296054764415302E+00 +step(A) = 176, err = 8.526097529883833E-02, h_new = 1.967556182218248E-03, n_yes = 0, n_no = 22, h*lambda = 1.585819982675070E+00 +step(A) = 177, err = 6.180674762187596E-02, h_new = 2.575867751683374E-03, n_yes = 0, n_no = 23, h*lambda = 1.977216120725463E+00 +step(A) = 178, err = 4.869859823172219E-02, h_new = 3.466807351524985E-03, n_yes = 0, n_no = 24, h*lambda = 2.582612950428378E+00 +step(A) = 179, err = 7.299927040061541E-02, h_new = 4.314283980065618E-03, n_yes = 1, n_no = 0, h*lambda = 3.465213353735224E+00 +step(A) = 180, err = 3.505180381762840E-01, h_new = 4.179104349195821E-03, n_yes = 2, n_no = 0, h*lambda = 4.295805939984726E+00 +step(R) = 181, err = 1.485964613744471E+00, h_new = 3.516286758978471E-03 +step(A) = 182, err = 5.349973884011944E-01, h_new = 3.375168250427538E-03, n_yes = 3, n_no = 0, h*lambda = 3.490303241682396E+00 +step(A) = 183, err = 5.803999025955329E-01, h_new = 3.249663232632390E-03, n_yes = 4, n_no = 0, h*lambda = 3.340113367891196E+00 +step(A) = 184, err = 4.892905749726796E-01, h_new = 3.231495793130896E-03, n_yes = 4, n_no = 1, h*lambda = 3.206498018435824E+00 +step(A) = 185, err = 3.892551199642380E-01, h_new = 3.318095958868127E-03, n_yes = 4, n_no = 2, h*lambda = 3.179243724110409E+00 +step(A) = 186, err = 3.547588633304440E-01, h_new = 3.429668708741459E-03, n_yes = 5, n_no = 0, h*lambda = 3.254612180458219E+00 +step(A) = 187, err = 3.866476014858118E-01, h_new = 3.480554241335775E-03, n_yes = 6, n_no = 0, h*lambda = 3.353554756146918E+00 +step(A) = 188, err = 4.540719782326855E-01, h_new = 3.448834301218070E-03, n_yes = 7, n_no = 0, h*lambda = 3.392504829840897E+00 +step(A) = 189, err = 4.976968146050750E-01, h_new = 3.386224911348940E-03, n_yes = 8, n_no = 0, h*lambda = 3.350968749649525E+00 +step(A) = 190, err = 4.794714119559821E-01, h_new = 3.358205296088036E-03, n_yes = 9, n_no = 0, h*lambda = 3.279879410233608E+00 +step(A) = 191, err = 4.298737629957955E-01, h_new = 3.387757385223475E-03, n_yes = 9, n_no = 1, h*lambda = 3.242633574384862E+00 +step(A) = 192, err = 3.974072197874833E-01, h_new = 3.448405822707422E-03, n_yes = 10, n_no = 0, h*lambda = 3.260875897281569E+00 +step(A) = 193, err = 4.004290084585745E-01, h_new = 3.494628133623199E-03, n_yes = 11, n_no = 0, h*lambda = 3.308587187955395E+00 +step(A) = 194, err = 4.292829756273832E-01, h_new = 3.500886866145740E-03, n_yes = 12, n_no = 0, h*lambda = 3.341980296964947E+00 +step(A) = 195, err = 4.573365638530497E-01, h_new = 3.479286767411650E-03, n_yes = 13, n_no = 0, h*lambda = 3.336964452469972E+00 +step(A) = 196, err = 4.607508183960181E-01, h_new = 3.462206219161974E-03, n_yes = 14, n_no = 0, h*lambda = 3.305496641646690E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 9.306509118845370E-01, ACCEPTED STEP = 189 +step(A) = 197, err = 4.414110580345363E-01, h_new = 3.471448611779810E-03, n_yes = 15, n_no = 0, h*lambda = 3.278481491393737E+00 +step(A) = 198, err = 4.205546755873187E-01, h_new = 3.503459967418862E-03, n_yes = 16, n_no = 0, h*lambda = 3.276376434522063E+00 +step(A) = 199, err = 4.145522690635501E-01, h_new = 3.537562218866223E-03, n_yes = 17, n_no = 0, h*lambda = 3.295523772606389E+00 +step(A) = 200, err = 4.245226729681265E-01, h_new = 3.555548581171666E-03, n_yes = 18, n_no = 0, h*lambda = 3.316314660236634E+00 +step(A) = 201, err = 4.396009549220398E-01, h_new = 3.555864434972601E-03, n_yes = 19, n_no = 0, h*lambda = 3.321766052386071E+00 +step(A) = 202, err = 4.465523480942285E-01, h_new = 3.551662957641965E-03, n_yes = 20, n_no = 0, h*lambda = 3.310637982341947E+00 +step(A) = 203, err = 4.411207989226998E-01, h_new = 3.557086033022827E-03, n_yes = 21, n_no = 0, h*lambda = 3.295316889242648E+00 +step(A) = 204, err = 4.303657217348121E-01, h_new = 3.575746941094147E-03, n_yes = 22, n_no = 0, h*lambda = 3.288891810450274E+00 +step(A) = 205, err = 4.239645584035698E-01, h_new = 3.600118268548514E-03, n_yes = 23, n_no = 0, h*lambda = 3.294558162339284E+00 +step(A) = 206, err = 4.258494625786690E-01, h_new = 3.619752878136625E-03, n_yes = 24, n_no = 0, h*lambda = 3.305257953938904E+00 +step(A) = 207, err = 4.327561969283347E-01, h_new = 3.630198018866805E-03, n_yes = 25, n_no = 0, h*lambda = 3.311391416744631E+00 +step(A) = 208, err = 4.381118801654675E-01, h_new = 3.635407523031155E-03, n_yes = 26, n_no = 0, h*lambda = 3.308973889359748E+00 +step(A) = 209, err = 4.378033206390567E-01, h_new = 3.642852387740564E-03, n_yes = 27, n_no = 0, h*lambda = 3.301702492670826E+00 +step(A) = 210, err = 4.331838707049168E-01, h_new = 3.656797891096875E-03, n_yes = 28, n_no = 0, h*lambda = 3.296381732091255E+00 +step(A) = 211, err = 4.288082266575673E-01, h_new = 3.675577885545703E-03, n_yes = 29, n_no = 0, h*lambda = 3.296813684138294E+00 +step(A) = 212, err = 4.279989888771147E-01, h_new = 3.694140400761753E-03, n_yes = 30, n_no = 0, h*lambda = 3.301420765469028E+00 +step(A) = 213, err = 4.305553889676714E-01, h_new = 3.708759583319070E-03, n_yes = 31, n_no = 0, h*lambda = 3.305633300473522E+00 +step(A) = 214, err = 4.336721825379390E-01, h_new = 3.719759708803672E-03, n_yes = 32, n_no = 0, h*lambda = 3.306143544613923E+00 +step(A) = 215, err = 4.346492908611463E-01, h_new = 3.730441485950846E-03, n_yes = 33, n_no = 0, h*lambda = 3.303290092264038E+00 +step(A) = 216, err = 4.330551307192517E-01, h_new = 3.743828608745091E-03, n_yes = 34, n_no = 0, h*lambda = 3.300029807125569E+00 +step(A) = 217, err = 4.306225375706228E-01, h_new = 3.760310844181102E-03, n_yes = 35, n_no = 0, h*lambda = 3.299020735246466E+00 +step(A) = 218, err = 4.293756907639733E-01, h_new = 3.777876527847335E-03, n_yes = 36, n_no = 0, h*lambda = 3.300566115806182E+00 +step(A) = 219, err = 4.299588438174837E-01, h_new = 3.794208535276982E-03, n_yes = 37, n_no = 0, h*lambda = 3.302870523431511E+00 +step(A) = 220, err = 4.314321127224678E-01, h_new = 3.808602621895213E-03, n_yes = 38, n_no = 0, h*lambda = 3.303907816511167E+00 +step(A) = 221, err = 4.323389026423615E-01, h_new = 3.822209930449244E-03, n_yes = 39, n_no = 0, h*lambda = 3.303085320257956E+00 +step(A) = 222, err = 4.319722586880699E-01, h_new = 3.836741351494489E-03, n_yes = 40, n_no = 0, h*lambda = 3.301419147277730E+00 +step(A) = 223, err = 4.307927176047864E-01, h_new = 3.852987912337984E-03, n_yes = 41, n_no = 0, h*lambda = 3.300385095950699E+00 +step(A) = 224, err = 4.298113342124661E-01, h_new = 3.870380413850536E-03, n_yes = 42, n_no = 0, h*lambda = 3.300644049791831E+00 +step(A) = 225, err = 4.296631557704300E-01, h_new = 3.887724647013034E-03, n_yes = 43, n_no = 0, h*lambda = 3.301686993645893E+00 +step(A) = 226, err = 4.301851388667979E-01, h_new = 3.904286808013262E-03, n_yes = 44, n_no = 0, h*lambda = 3.302485977631584E+00 +step(A) = 227, err = 4.307133054066499E-01, h_new = 3.920292122979136E-03, n_yes = 45, n_no = 0, h*lambda = 3.302422090464993E+00 +step(A) = 228, err = 4.307142557317793E-01, h_new = 3.936554777334662E-03, n_yes = 46, n_no = 0, h*lambda = 3.301694001216952E+00 +step(A) = 229, err = 4.301851746333406E-01, h_new = 3.953711297252496E-03, n_yes = 47, n_no = 0, h*lambda = 3.300988215441240E+00 +step(A) = 230, err = 4.295452210841031E-01, h_new = 3.971752422300344E-03, n_yes = 48, n_no = 0, h*lambda = 3.300828777040461E+00 +step(A) = 231, err = 4.292107952935771E-01, h_new = 3.990166572077440E-03, n_yes = 49, n_no = 0, h*lambda = 3.301193521191673E+00 +step(A) = 232, err = 4.292646244779778E-01, h_new = 4.008455751590996E-03, n_yes = 50, n_no = 0, h*lambda = 3.301646410466073E+00 +step(A) = 233, err = 4.294605789942588E-01, h_new = 4.026536547814937E-03, n_yes = 51, n_no = 0, h*lambda = 3.301771849193995E+00 +step(A) = 234, err = 4.294881484821736E-01, h_new = 4.044728598672606E-03, n_yes = 52, n_no = 0, h*lambda = 3.301501875902923E+00 +step(A) = 235, err = 4.292334976052844E-01, h_new = 4.063422951925307E-03, n_yes = 53, n_no = 0, h*lambda = 3.301097579500324E+00 +step(A) = 236, err = 4.288292849856661E-01, h_new = 4.082760731028622E-03, n_yes = 54, n_no = 0, h*lambda = 3.300871772815385E+00 +step(A) = 237, err = 4.284966709701275E-01, h_new = 4.102577075871903E-03, n_yes = 55, n_no = 0, h*lambda = 3.300928638830731E+00 +step(A) = 238, err = 4.283501725373395E-01, h_new = 4.122601297893762E-03, n_yes = 56, n_no = 0, h*lambda = 3.301124449621309E+00 +step(A) = 239, err = 4.283289637297810E-01, h_new = 4.142701463051705E-03, n_yes = 57, n_no = 0, h*lambda = 3.301234132454486E+00 +step(A) = 240, err = 4.282820596349022E-01, h_new = 4.162968884373310E-03, n_yes = 58, n_no = 0, h*lambda = 3.301147770350908E+00 +step(A) = 241, err = 4.281074167924426E-01, h_new = 4.183607199895076E-03, n_yes = 59, n_no = 0, h*lambda = 3.300934116181848E+00 +step(A) = 242, err = 4.278229899028322E-01, h_new = 4.204754278001979E-03, n_yes = 60, n_no = 0, h*lambda = 3.300748273610640E+00 +step(A) = 243, err = 4.275270575766886E-01, h_new = 4.226393038144650E-03, n_yes = 61, n_no = 0, h*lambda = 3.300689871481256E+00 +step(A) = 244, err = 4.273027857195540E-01, h_new = 4.248404526255284E-03, n_yes = 62, n_no = 0, h*lambda = 3.300735255137489E+00 +step(A) = 245, err = 4.271550363186925E-01, h_new = 4.270692093309106E-03, n_yes = 63, n_no = 0, h*lambda = 3.300782193626054E+00 +step(A) = 246, err = 4.270228587459070E-01, h_new = 4.293263069108798E-03, n_yes = 64, n_no = 0, h*lambda = 3.300747843562565E+00 +step(A) = 247, err = 4.268417375909908E-01, h_new = 4.316211182245253E-03, n_yes = 65, n_no = 0, h*lambda = 3.300630716059762E+00 +step(A) = 248, err = 4.265956213797722E-01, h_new = 4.339633801499685E-03, n_yes = 66, n_no = 0, h*lambda = 3.300494780817451E+00 +step(A) = 249, err = 4.263193737176272E-01, h_new = 4.363563362692269E-03, n_yes = 67, n_no = 0, h*lambda = 3.300404709203970E+00 +step(A) = 250, err = 4.260600881587809E-01, h_new = 4.387964990585588E-03, n_yes = 68, n_no = 0, h*lambda = 3.300374150266467E+00 +step(A) = 251, err = 4.258368123725155E-01, h_new = 4.412788910192447E-03, n_yes = 69, n_no = 0, h*lambda = 3.300365536963663E+00 +step(A) = 252, err = 4.256311074016821E-01, h_new = 4.438024743325478E-03, n_yes = 70, n_no = 0, h*lambda = 3.300330755475881E+00 +step(A) = 253, err = 4.254094374485623E-01, h_new = 4.463713918291515E-03, n_yes = 71, n_no = 0, h*lambda = 3.300251688463127E+00 +step(A) = 254, err = 4.251528637705740E-01, h_new = 4.489918712614180E-03, n_yes = 72, n_no = 0, h*lambda = 3.300148342366199E+00 +step(A) = 255, err = 4.248688506907005E-01, h_new = 4.516681435733419E-03, n_yes = 73, n_no = 0, h*lambda = 3.300054879682836E+00 +step(A) = 256, err = 4.245795292937601E-01, h_new = 4.544008415283115E-03, n_yes = 74, n_no = 0, h*lambda = 3.299989397775974E+00 +step(A) = 257, err = 4.243007359497090E-01, h_new = 4.571886655870232E-03, n_yes = 75, n_no = 0, h*lambda = 3.299942855104354E+00 +step(A) = 258, err = 4.240304753682130E-01, h_new = 4.600313341654090E-03, n_yes = 76, n_no = 0, h*lambda = 3.299891949478224E+00 +step(A) = 259, err = 4.237540025421365E-01, h_new = 4.629312064912752E-03, n_yes = 77, n_no = 0, h*lambda = 3.299820658193305E+00 +step(A) = 260, err = 4.234579704995718E-01, h_new = 4.658925511089058E-03, n_yes = 78, n_no = 0, h*lambda = 3.299731457982905E+00 +step(A) = 261, err = 4.231404670605873E-01, h_new = 4.689195217083358E-03, n_yes = 79, n_no = 0, h*lambda = 3.299639449519019E+00 +step(A) = 262, err = 4.228098096402270E-01, h_new = 4.720147236696066E-03, n_yes = 80, n_no = 0, h*lambda = 3.299557656336072E+00 +step(A) = 263, err = 4.224754266122596E-01, h_new = 4.751794061992471E-03, n_yes = 81, n_no = 0, h*lambda = 3.299487064891211E+00 +step(A) = 264, err = 4.221397572250009E-01, h_new = 4.784148090630616E-03, n_yes = 82, n_no = 0, h*lambda = 3.299418345989634E+00 +step(A) = 265, err = 4.217974025208784E-01, h_new = 4.817233646248613E-03, n_yes = 83, n_no = 0, h*lambda = 3.299341434462446E+00 +step(A) = 266, err = 4.214406700743320E-01, h_new = 4.851088314633154E-03, n_yes = 84, n_no = 0, h*lambda = 3.299253687082273E+00 +step(A) = 267, err = 4.210657477187539E-01, h_new = 4.885754748734701E-03, n_yes = 85, n_no = 0, h*lambda = 3.299160419085479E+00 +step(A) = 268, err = 4.206744564353846E-01, h_new = 4.921271494404488E-03, n_yes = 86, n_no = 0, h*lambda = 3.299068991663855E+00 +step(A) = 269, err = 4.202711170789381E-01, h_new = 4.957670479629963E-03, n_yes = 87, n_no = 0, h*lambda = 3.298982513508011E+00 +step(A) = 270, err = 4.198580540936551E-01, h_new = 4.994981975275775E-03, n_yes = 88, n_no = 0, h*lambda = 3.298898254205975E+00 +step(A) = 271, err = 4.194335995255202E-01, h_new = 5.033241715662309E-03, n_yes = 89, n_no = 0, h*lambda = 3.298811045041153E+00 +step(A) = 272, err = 4.189936537320252E-01, h_new = 5.072494208865829E-03, n_yes = 90, n_no = 0, h*lambda = 3.298717913853457E+00 +step(A) = 273, err = 4.185348058702847E-01, h_new = 5.112790510439460E-03, n_yes = 91, n_no = 0, h*lambda = 3.298620005927656E+00 +step(A) = 274, err = 4.180561978342951E-01, h_new = 5.154183518165018E-03, n_yes = 92, n_no = 0, h*lambda = 3.298520832098713E+00 +step(A) = 275, err = 4.175589093278980E-01, h_new = 5.196725241731624E-03, n_yes = 93, n_no = 0, h*lambda = 3.298423010402977E+00 +step(A) = 276, err = 4.170438715306105E-01, h_new = 5.240468066169636E-03, n_yes = 94, n_no = 0, h*lambda = 3.298326412487723E+00 +step(A) = 277, err = 4.165102735711282E-01, h_new = 5.285468464512027E-03, n_yes = 95, n_no = 0, h*lambda = 3.298228897593983E+00 +step(A) = 278, err = 4.159556178380689E-01, h_new = 5.331789991909682E-03, n_yes = 96, n_no = 0, h*lambda = 3.298128510128849E+00 +step(A) = 279, err = 4.153770237895118E-01, h_new = 5.379503623611202E-03, n_yes = 97, n_no = 0, h*lambda = 3.298025088501638E+00 +step(A) = 280, err = 4.147724711091176E-01, h_new = 5.428686039233089E-03, n_yes = 98, n_no = 0, h*lambda = 3.297920132091144E+00 +step(A) = 281, err = 4.141409969555077E-01, h_new = 5.479418021445208E-03, n_yes = 99, n_no = 0, h*lambda = 3.297815401152697E+00 +step(A) = 282, err = 4.134819002790782E-01, h_new = 5.531784671826462E-03, n_yes = 100, n_no = 0, h*lambda = 3.297711633507199E+00 +step(A) = 283, err = 4.127937823687496E-01, h_new = 5.585877421317612E-03, n_yes = 101, n_no = 0, h*lambda = 3.297608380073231E+00 +step(A) = 284, err = 4.120742054534275E-01, h_new = 5.641796452121301E-03, n_yes = 102, n_no = 0, h*lambda = 3.297504863095371E+00 +step(A) = 285, err = 4.113200925774184E-01, h_new = 5.699652167295843E-03, n_yes = 103, n_no = 0, h*lambda = 3.297400969642974E+00 +step(A) = 286, err = 4.105283746595030E-01, h_new = 5.759565460900966E-03, n_yes = 104, n_no = 0, h*lambda = 3.297297572992058E+00 +step(A) = 287, err = 4.096962958683515E-01, h_new = 5.821667654197287E-03, n_yes = 105, n_no = 0, h*lambda = 3.297196079676924E+00 +step(A) = 288, err = 4.088211805508948E-01, h_new = 5.886101189535850E-03, n_yes = 106, n_no = 0, h*lambda = 3.297097739264321E+00 +step(A) = 289, err = 4.078999292805139E-01, h_new = 5.953021509572973E-03, n_yes = 107, n_no = 0, h*lambda = 3.297003333553935E+00 +step(A) = 290, err = 4.069286643367531E-01, h_new = 6.022599701670972E-03, n_yes = 108, n_no = 0, h*lambda = 3.296913436259845E+00 +step(A) = 291, err = 4.059027307932550E-01, h_new = 6.095025172835020E-03, n_yes = 109, n_no = 0, h*lambda = 3.296828947092619E+00 +step(A) = 292, err = 4.048169258526370E-01, h_new = 6.170507994936443E-03, n_yes = 110, n_no = 0, h*lambda = 3.296751455234593E+00 +step(A) = 293, err = 4.036656653309485E-01, h_new = 6.249281203922641E-03, n_yes = 111, n_no = 0, h*lambda = 3.296683224161232E+00 +step(A) = 294, err = 4.024428954343365E-01, h_new = 6.331603700551526E-03, n_yes = 112, n_no = 0, h*lambda = 3.296626944716787E+00 +step(A) = 295, err = 4.011417849609941E-01, h_new = 6.417764266935371E-03, n_yes = 113, n_no = 0, h*lambda = 3.296585571191088E+00 +step(A) = 296, err = 3.997543801402357E-01, h_new = 6.508086807560819E-03, n_yes = 114, n_no = 0, h*lambda = 3.296562443275698E+00 +step(A) = 297, err = 3.982713709433062E-01, h_new = 6.602936651123937E-03, n_yes = 115, n_no = 0, h*lambda = 3.296561653745240E+00 +step(A) = 298, err = 3.966819670435605E-01, h_new = 6.702727844177274E-03, n_yes = 116, n_no = 0, h*lambda = 3.296588469298817E+00 +step(A) = 299, err = 3.949737553404244E-01, h_new = 6.807931743832539E-03, n_yes = 117, n_no = 0, h*lambda = 3.296649654825813E+00 +step(A) = 300, err = 3.931324002247971E-01, h_new = 6.919087605637563E-03, n_yes = 118, n_no = 0, h*lambda = 3.296753719325883E+00 +step(A) = 301, err = 3.911411317652012E-01, h_new = 7.036816069002807E-03, n_yes = 119, n_no = 0, h*lambda = 3.296911242802162E+00 +step(A) = 302, err = 3.889800524912123E-01, h_new = 7.161836500792817E-03, n_yes = 120, n_no = 0, h*lambda = 3.297135461658360E+00 +step(A) = 303, err = 3.866253029065805E-01, h_new = 7.294989285968968E-03, n_yes = 121, n_no = 0, h*lambda = 3.297443211238030E+00 +step(A) = 304, err = 3.840480530729704E-01, h_new = 7.437264588288742E-03, n_yes = 122, n_no = 0, h*lambda = 3.297856252398439E+00 +step(A) = 305, err = 3.812131839851460E-01, h_new = 7.589839976134515E-03, n_yes = 123, n_no = 0, h*lambda = 3.298403033531530E+00 +step(A) = 306, err = 3.780774391405725E-01, h_new = 7.754130675266364E-03, n_yes = 124, n_no = 0, h*lambda = 3.299121075776918E+00 +step(A) = 307, err = 3.745867642471649E-01, h_new = 7.931858222387527E-03, n_yes = 125, n_no = 0, h*lambda = 3.300060376571410E+00 +step(A) = 308, err = 3.706724549957577E-01, h_new = 8.125146419276591E-03, n_yes = 126, n_no = 0, h*lambda = 3.301288478614701E+00 +step(A) = 309, err = 3.662455075628569E-01, h_new = 8.336658774680046E-03, n_yes = 127, n_no = 0, h*lambda = 3.302898201715120E+00 +step(A) = 310, err = 3.611880979145950E-01, h_new = 8.569801139885829E-03, n_yes = 128, n_no = 0, h*lambda = 3.305019656714996E+00 +step(A) = 311, err = 3.553402126001545E-01, h_new = 8.829031172535121E-03, n_yes = 129, n_no = 0, h*lambda = 3.307839385840251E+00 +step(A) = 312, err = 3.484776836108640E-01, h_new = 9.120351759979481E-03, n_yes = 130, n_no = 0, h*lambda = 3.311631938628216E+00 +step(A) = 313, err = 3.402741534167765E-01, h_new = 9.452140609354995E-03, n_yes = 131, n_no = 0, h*lambda = 3.316814291706466E+00 +step(A) = 314, err = 3.302309183282819E-01, h_new = 9.836641122379494E-03, n_yes = 132, n_no = 0, h*lambda = 3.324044700868102E+00 +step(A) = 315, err = 3.175367833680536E-01, h_new = 1.029288353523805E-02, n_yes = 133, n_no = 0, h*lambda = 3.334414182583914E+00 +step(A) = 316, err = 3.007574192129444E-01, h_new = 1.085311863759427E-02, n_yes = 134, n_no = 0, h*lambda = 3.349848960293911E+00 +step(A) = 317, err = 2.770425480094375E-01, h_new = 1.157958015835199E-02, n_yes = 135, n_no = 0, h*lambda = 3.374052218156454E+00 +step(A) = 318, err = 2.396474186032754E-01, h_new = 1.262146633910143E-02, n_yes = 136, n_no = 0, h*lambda = 3.415050308842849E+00 +step(A) = 319, err = 1.672169330518306E-01, h_new = 1.454044138087391E-02, n_yes = 137, n_no = 0, h*lambda = 3.493245381393254E+00 +step(A) = 320, err = 6.547522351608699E-02, h_new = 1.936503210121610E-02, n_yes = 138, n_no = 0, h*lambda = 3.779558822626685E+00 +step(R) = 321, err = 2.758072167696834E+00, h_new = 1.466755665203884E-02 +step(A) = 322, err = 5.544936042036211E-01, h_new = 1.308521766712097E-02, n_yes = 139, n_no = 0, h*lambda = 3.439844267455001E+00 +step(A) = 323, err = 4.608234807061337E-01, h_new = 1.312134759461917E-02, n_yes = 139, n_no = 1, h*lambda = 2.796465720743475E+00 +step(A) = 324, err = 2.969699612327859E-01, h_new = 1.407347043448586E-02, n_yes = 139, n_no = 2, h*lambda = 2.524954751929724E+00 +step(A) = 325, err = 2.801906534171543E-01, h_new = 1.497907847147494E-02, n_yes = 139, n_no = 3, h*lambda = 2.369860469525626E+00 +step(A) = 326, err = 2.705027714409222E-01, h_new = 1.600134757536991E-02, n_yes = 139, n_no = 4, h*lambda = 2.114647776142334E+00 +step(A) = 327, err = 2.322671428927122E-01, h_new = 1.751733417013966E-02, n_yes = 139, n_no = 5, h*lambda = 1.767476442444690E+00 +step(A) = 328, err = 1.694881217518184E-01, h_new = 2.010927253694785E-02, n_yes = 0, n_no = 6, h*lambda = 1.306442795376463E+00 +step(R) = 329, err = 1.084461510298132E+00, h_new = 1.785058548720665E-02 +step(A) = 330, err = 4.164041221219500E-01, h_new = 1.736772732164151E-02, n_yes = 0, n_no = 7, h*lambda = 1.821084854705834E-01 +step(R) = 331, err = 5.013796320452609E+00, h_new = 1.188385008552715E-02 +step(R) = 332, err = 2.939269588993492E+00, h_new = 8.904283366664307E-03 +step(A) = 333, err = 6.344689183230303E-01, h_new = 8.360116372816851E-03, n_yes = 0, n_no = 8, h*lambda = 4.999577635130124E-01 +step(R) = 334, err = 1.506978358142714E+00, h_new = 7.017406578821495E-03 +step(R) = 335, err = 1.507078084970641E+00, h_new = 5.890281776140481E-03 +step(R) = 336, err = 1.056204469623091E+00, h_new = 5.252201992389361E-03 +step(A) = 337, err = 7.013961933120292E-01, h_new = 4.930223016397128E-03, n_yes = 0, n_no = 9, h*lambda = 4.458951238252798E-01 +step(R) = 338, err = 1.368003160369734E+01, h_new = 2.844292572253510E-03 +step(A) = 339, err = 2.154297042023680E-01, h_new = 3.276373188376880E-03, n_yes = 0, n_no = 10, h*lambda = 5.781008123990621E-01 +step(A) = 340, err = 6.517791037248070E-01, h_new = 2.589118491383514E-03, n_yes = 1, n_no = 0, h*lambda = 9.662911863007947E+00 +step(R) = 341, err = 9.363066039698422E+00, h_new = 1.593137869750087E-03 +step(R) = 342, err = 1.103967583380753E+00, h_new = 1.409916185041910E-03 +step(A) = 343, err = 5.792252937311234E-01, h_new = 1.368723565504569E-03, n_yes = 1, n_no = 1, h*lambda = 6.116552865099667E-01 +step(A) = 344, err = 6.875690543096030E-01, h_new = 1.284483056994523E-03, n_yes = 1, n_no = 2, h*lambda = 1.225537118258073E+00 +step(R) = 345, err = 2.533668995469276E+00, h_new = 9.870369488894908E-04 +step(A) = 346, err = 5.634250690067383E-01, h_new = 9.647758513983588E-04, n_yes = 1, n_no = 3, h*lambda = 9.696329291321375E-01 +step(A) = 347, err = 6.397646579896418E-01, h_new = 9.155434055823949E-04, n_yes = 1, n_no = 4, h*lambda = 9.695144007444304E-01 +step(A) = 348, err = 5.120257578337231E-01, h_new = 9.069485495246315E-04, n_yes = 1, n_no = 5, h*lambda = 9.258268721123650E-01 +step(A) = 349, err = 4.684576636521415E-01, h_new = 9.040300635595680E-04, n_yes = 0, n_no = 6, h*lambda = 9.192261126382347E-01 +step(A) = 350, err = 3.992343733368792E-01, h_new = 9.226637306866363E-04, n_yes = 0, n_no = 7, h*lambda = 9.166675883651417E-01 +step(A) = 351, err = 1.739001318661580E-01, h_new = 1.077665775295479E-03, n_yes = 0, n_no = 8, h*lambda = 9.352847703090210E-01 +step(A) = 352, err = 1.450216591933589E-01, h_new = 1.255726353495396E-03, n_yes = 0, n_no = 9, h*lambda = 1.091654665463814E+00 +step(A) = 353, err = 1.079829569870686E-01, h_new = 1.527300332301485E-03, n_yes = 0, n_no = 10, h*lambda = 1.270717098143420E+00 +step(A) = 354, err = 8.757978699270973E-02, h_new = 1.902359116371433E-03, n_yes = 0, n_no = 11, h*lambda = 1.543482436580736E+00 +step(A) = 355, err = 6.456233631723808E-02, h_new = 2.474769177299382E-03, n_yes = 0, n_no = 12, h*lambda = 1.919294523613246E+00 +step(A) = 356, err = 4.949968416402232E-02, h_new = 3.327315357578232E-03, n_yes = 0, n_no = 13, h*lambda = 2.491356651671514E+00 +step(A) = 357, err = 6.438796693672601E-02, h_new = 4.232761223977908E-03, n_yes = 1, n_no = 0, h*lambda = 3.339793883858084E+00 +step(A) = 358, err = 2.797730075537238E-01, h_new = 4.238981097626998E-03, n_yes = 2, n_no = 0, h*lambda = 4.232766260040452E+00 +step(R) = 359, err = 1.316524838249334E+00, h_new = 3.640835437870546E-03 +step(A) = 360, err = 5.341844412925590E-01, h_new = 3.464242470661683E-03, n_yes = 3, n_no = 0, h*lambda = 3.629137340689636E+00 +step(A) = 361, err = 7.018131540087504E-01, h_new = 3.229243431640143E-03, n_yes = 4, n_no = 0, h*lambda = 3.442495298916736E+00 +step(A) = 362, err = 5.894036583006134E-01, h_new = 3.134887599014504E-03, n_yes = 4, n_no = 1, h*lambda = 3.199693006923365E+00 +step(A) = 363, err = 4.026123116021955E-01, h_new = 3.224409542044754E-03, n_yes = 4, n_no = 2, h*lambda = 3.097414849933740E+00 +step(A) = 364, err = 3.179969364900750E-01, h_new = 3.399979433851089E-03, n_yes = 4, n_no = 3, h*lambda = 3.176578537857591E+00 +step(A) = 365, err = 3.367762714656367E-01, h_new = 3.516961908380592E-03, n_yes = 5, n_no = 0, h*lambda = 3.339234748250505E+00 +step(A) = 366, err = 4.309276422965480E-01, h_new = 3.496673589755197E-03, n_yes = 6, n_no = 0, h*lambda = 3.443108463539667E+00 +step(A) = 367, err = 5.277919916085688E-01, h_new = 3.391994204439725E-03, n_yes = 7, n_no = 0, h*lambda = 3.412353949788284E+00 +step(A) = 368, err = 5.296304036466514E-01, h_new = 3.315283929811912E-03, n_yes = 8, n_no = 0, h*lambda = 3.299927072673363E+00 +step(A) = 369, err = 4.526156127222287E-01, h_new = 3.328496725446670E-03, n_yes = 8, n_no = 1, h*lambda = 3.215457491426718E+00 +step(A) = 370, err = 3.869624821910816E-01, h_new = 3.410484801370306E-03, n_yes = 8, n_no = 2, h*lambda = 3.218340142079507E+00 +step(A) = 371, err = 3.744993934307922E-01, h_new = 3.492035838156108E-03, n_yes = 9, n_no = 0, h*lambda = 3.287189159795818E+00 +step(A) = 372, err = 4.100194386256036E-01, h_new = 3.516272226636235E-03, n_yes = 10, n_no = 0, h*lambda = 3.354865484299512E+00 +step(A) = 373, err = 4.608766850419500E-01, h_new = 3.483595911389426E-03, n_yes = 11, n_no = 0, h*lambda = 3.367069048243797E+00 +step(A) = 374, err = 4.816281309864093E-01, h_new = 3.441538699119594E-03, n_yes = 12, n_no = 0, h*lambda = 3.324891144951819E+00 +step(A) = 375, err = 4.584277456682849E-01, h_new = 3.434690339403088E-03, n_yes = 13, n_no = 0, h*lambda = 3.274104202325452E+00 +step(A) = 376, err = 4.217010246802865E-01, h_new = 3.470005332839345E-03, n_yes = 14, n_no = 0, h*lambda = 3.256970425629175E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 1.809882994363079E+00, ACCEPTED STEP = 357 +step(A) = 377, err = 4.035671505294718E-01, h_new = 3.520198475311247E-03, n_yes = 15, n_no = 0, h*lambda = 3.279612978036328E+00 +step(A) = 378, err = 4.124998511616665E-01, h_new = 3.551601632330066E-03, n_yes = 16, n_no = 0, h*lambda = 3.315887769755170E+00 +step(A) = 379, err = 4.364865333541689E-01, h_new = 3.552128464250999E-03, n_yes = 17, n_no = 0, h*lambda = 3.334099100813723E+00 +step(A) = 380, err = 4.537348839191425E-01, h_new = 3.537314207101296E-03, n_yes = 18, n_no = 0, h*lambda = 3.323211423181647E+00 +step(A) = 381, err = 4.507776443118724E-01, h_new = 3.531950690399045E-03, n_yes = 19, n_no = 0, h*lambda = 3.298050238123096E+00 +step(A) = 382, err = 4.342894091804872E-01, h_new = 3.548078055167689E-03, n_yes = 20, n_no = 0, h*lambda = 3.281767755345274E+00 +step(A) = 383, err = 4.206542956351669E-01, h_new = 3.578322989303431E-03, n_yes = 21, n_no = 0, h*lambda = 3.285356720285332E+00 +step(A) = 384, err = 4.196258972151440E-01, h_new = 3.605723937723291E-03, n_yes = 22, n_no = 0, h*lambda = 3.301762969345275E+00 +step(A) = 385, err = 4.292224611770568E-01, h_new = 3.619040631090450E-03, n_yes = 23, n_no = 0, h*lambda = 3.315260929917143E+00 +step(A) = 386, err = 4.397641151446334E-01, h_new = 3.620728012934108E-03, n_yes = 24, n_no = 0, h*lambda = 3.315622571336352E+00 +step(A) = 387, err = 4.424148223976933E-01, h_new = 3.622231135717094E-03, n_yes = 25, n_no = 0, h*lambda = 3.305262547419302E+00 +step(A) = 388, err = 4.365197194349388E-01, h_new = 3.632881216517289E-03, n_yes = 26, n_no = 0, h*lambda = 3.294705192440800E+00 +step(A) = 389, err = 4.286360496829625E-01, h_new = 3.652908418797025E-03, n_yes = 27, n_no = 0, h*lambda = 3.292379763151353E+00 +step(A) = 390, err = 4.253576036254677E-01, h_new = 3.675163184206087E-03, n_yes = 28, n_no = 0, h*lambda = 3.298373393203926E+00 +step(A) = 391, err = 4.281672664980711E-01, h_new = 3.692283301842687E-03, n_yes = 29, n_no = 0, h*lambda = 3.306152666725394E+00 +step(A) = 392, err = 4.335203958450980E-01, h_new = 3.702631088355893E-03, n_yes = 30, n_no = 0, h*lambda = 3.309111987348828E+00 +step(A) = 393, err = 4.365379891511153E-01, h_new = 3.710475656904800E-03, n_yes = 31, n_no = 0, h*lambda = 3.305861487498091E+00 +step(A) = 394, err = 4.351678259500148E-01, h_new = 3.721356918232027E-03, n_yes = 32, n_no = 0, h*lambda = 3.300274009212814E+00 +step(A) = 395, err = 4.313533339230897E-01, h_new = 3.737390428949686E-03, n_yes = 33, n_no = 0, h*lambda = 3.297272979759182E+00 +step(A) = 396, err = 4.285761795087774E-01, h_new = 3.756293683258537E-03, n_yes = 34, n_no = 0, h*lambda = 3.298677130890415E+00 +step(A) = 397, err = 4.287190833217491E-01, h_new = 3.774103377896347E-03, n_yes = 35, n_no = 0, h*lambda = 3.302416479113768E+00 +step(A) = 398, err = 4.309643259122914E-01, h_new = 3.788682301899664E-03, n_yes = 36, n_no = 0, h*lambda = 3.304993020455877E+00 +step(A) = 399, err = 4.329973638391673E-01, h_new = 3.801069921999938E-03, n_yes = 37, n_no = 0, h*lambda = 3.304563353713653E+00 +step(A) = 400, err = 4.331357163464960E-01, h_new = 3.814008869784482E-03, n_yes = 38, n_no = 0, h*lambda = 3.302070284168640E+00 +step(A) = 401, err = 4.315662949195275E-01, h_new = 3.829403146181749E-03, n_yes = 39, n_no = 0, h*lambda = 3.299906818493146E+00 +step(A) = 402, err = 4.298065745571851E-01, h_new = 3.846972490640173E-03, n_yes = 40, n_no = 0, h*lambda = 3.299698514896586E+00 +step(A) = 403, err = 4.291955448638680E-01, h_new = 3.864925504721692E-03, n_yes = 41, n_no = 0, h*lambda = 3.301170400615810E+00 +step(A) = 404, err = 4.298730042492989E-01, h_new = 3.881700430530588E-03, n_yes = 42, n_no = 0, h*lambda = 3.302765846451996E+00 +step(A) = 405, err = 4.309260669387576E-01, h_new = 3.897172792658790E-03, n_yes = 43, n_no = 0, h*lambda = 3.303154342426523E+00 +step(A) = 406, err = 4.313284316802334E-01, h_new = 3.912468980688625E-03, n_yes = 44, n_no = 0, h*lambda = 3.302246150455781E+00 +step(A) = 407, err = 4.307845084319388E-01, h_new = 3.928814528533372E-03, n_yes = 45, n_no = 0, h*lambda = 3.301004969890132E+00 +step(A) = 408, err = 4.298165423303336E-01, h_new = 3.946538175855745E-03, n_yes = 46, n_no = 0, h*lambda = 3.300457272966248E+00 +step(A) = 409, err = 4.291720917134020E-01, h_new = 3.964996354060733E-03, n_yes = 47, n_no = 0, h*lambda = 3.300860336245485E+00 +step(A) = 410, err = 4.291798750075976E-01, h_new = 3.983289498679896E-03, n_yes = 48, n_no = 0, h*lambda = 3.301659001676910E+00 +step(A) = 411, err = 4.295785616107266E-01, h_new = 4.001038338066340E-03, n_yes = 49, n_no = 0, h*lambda = 3.302098366435485E+00 +step(A) = 412, err = 4.298476998635363E-01, h_new = 4.018587629621689E-03, n_yes = 50, n_no = 0, h*lambda = 3.301867531751385E+00 +step(A) = 413, err = 4.296760459012742E-01, h_new = 4.036589093769783E-03, n_yes = 51, n_no = 0, h*lambda = 3.301254848796467E+00 +step(A) = 414, err = 4.291671457167899E-01, h_new = 4.055423356777790E-03, n_yes = 52, n_no = 0, h*lambda = 3.300792187767611E+00 +step(A) = 415, err = 4.286692624819732E-01, h_new = 4.074956413624082E-03, n_yes = 53, n_no = 0, h*lambda = 3.300779491762202E+00 +step(A) = 416, err = 4.284453888845481E-01, h_new = 4.094757061906611E-03, n_yes = 54, n_no = 0, h*lambda = 3.301094355125355E+00 +step(A) = 417, err = 4.284820221704877E-01, h_new = 4.114508142343789E-03, n_yes = 55, n_no = 0, h*lambda = 3.301378208179972E+00 +step(A) = 418, err = 4.285581027441299E-01, h_new = 4.134243848945743E-03, n_yes = 56, n_no = 0, h*lambda = 3.301371663800388E+00 +step(A) = 419, err = 4.284643391207051E-01, h_new = 4.154258248987697E-03, n_yes = 57, n_no = 0, h*lambda = 3.301100608812401E+00 +step(A) = 420, err = 4.281676627059799E-01, h_new = 4.174824569557473E-03, n_yes = 58, n_no = 0, h*lambda = 3.300795749714216E+00 +step(A) = 421, err = 4.278005791273590E-01, h_new = 4.195988237915218E-03, n_yes = 59, n_no = 0, h*lambda = 3.300665609574369E+00 +step(A) = 422, err = 4.275203746049487E-01, h_new = 4.217584256108569E-03, n_yes = 60, n_no = 0, h*lambda = 3.300734901041688E+00 +step(A) = 423, err = 4.273784348054304E-01, h_new = 4.239419633725231E-03, n_yes = 61, n_no = 0, h*lambda = 3.300863388402065E+00 +step(A) = 424, err = 4.273015686248449E-01, h_new = 4.261441761634310E-03, n_yes = 62, n_no = 0, h*lambda = 3.300892260934269E+00 +step(A) = 425, err = 4.271763403969597E-01, h_new = 4.283760915589911E-03, n_yes = 63, n_no = 0, h*lambda = 3.300774630150715E+00 +step(A) = 426, err = 6.964951300427906E-03, h_new = 4.190371271724428E-03, n_yes = 63, n_no = 1, h*lambda = 1.599612021680380E+00 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 2558 +Number of performed steps = 426 +Number of accepted steps = 406 +Number of rejected steps = 20 +y = 1.820788982019278E+00 -7.853646714272298E-01 +h = 4.190371271724428E-03 diff --git a/russell_ode/data/fortran_radau5_amplifier1t.txt b/russell_ode/data/fortran_radau5_amplifier1t.txt new file mode 100644 index 00000000..859fad78 --- /dev/null +++ b/russell_ode/data/fortran_radau5_amplifier1t.txt @@ -0,0 +1,64 @@ + +running radau5.f test +step = 0, x = 0.0000, y1and5 = 0.000000000000000E+00 0.000000000000000E+00 +step = 12, x = 0.0010, y1and5 = 1.916311926796919E-01 -2.688742328316260E+00 +step = 16, x = 0.0020, y1and5 = 3.218545459856986E-01 -1.744730225567205E+00 +step = 17, x = 0.0030, y1and5 = 3.335987191468288E-01 -7.596643438832655E-01 +step = 20, x = 0.0040, y1and5 = 2.215636007078414E-01 -1.395458336433338E-01 +step = 23, x = 0.0050, y1and5 = 2.822200240466559E-02 5.035341147575879E-02 +step = 24, x = 0.0060, y1and5 = -1.725991658652529E-01 6.096730370654814E-02 +step = 27, x = 0.0070, y1and5 = -3.052894121289587E-01 -4.074905119208934E-01 +step = 30, x = 0.0080, y1and5 = -3.208689480270557E-01 -2.003438545897961E+00 +step = 33, x = 0.0090, y1and5 = -2.112947302819898E-01 -2.771120812089473E+00 +step = 35, x = 0.0100, y1and5 = -1.964755260934534E-02 -2.909025267194513E+00 +step = 36, x = 0.0110, y1and5 = 1.805509844293114E-01 -2.399268747987867E+00 +step = 38, x = 0.0120, y1and5 = 3.126188413115715E-01 -1.460587294440855E+00 +step = 39, x = 0.0130, y1and5 = 3.258822671851661E-01 -4.922170304759909E-01 +step = 43, x = 0.0140, y1and5 = 2.151031987054799E-01 1.085467151411904E-01 +step = 45, x = 0.0150, y1and5 = 2.281762592706080E-02 2.830230578538427E-01 +step = 46, x = 0.0160, y1and5 = -1.771028609189726E-01 2.901769221275497E-01 +step = 48, x = 0.0170, y1and5 = -3.089261062497468E-01 -1.143019747048787E-01 +step = 53, x = 0.0180, y1and5 = -3.239999209419813E-01 -1.773489734488750E+00 +step = 55, x = 0.0190, y1and5 = -2.139062022287369E-01 -2.549976137719734E+00 +step = 57, x = 0.0200, y1and5 = -2.183476088577141E-02 -2.694693797348923E+00 +step = 58, x = 0.0210, y1and5 = 1.787239964399157E-01 -2.190379442496710E+00 +step = 60, x = 0.0220, y1and5 = 3.110923611527006E-01 -1.257994985386343E+00 +step = 61, x = 0.0230, y1and5 = 3.246041882463260E-01 -2.951538003461895E-01 +step = 64, x = 0.0240, y1and5 = 2.140352114701277E-01 2.997035672861627E-01 +step = 66, x = 0.0250, y1and5 = 2.192275843522865E-02 4.688416136401767E-01 +step = 68, x = 0.0260, y1and5 = -1.778526757960134E-01 4.723850851682659E-01 +step = 71, x = 0.0270, y1and5 = -3.095275377541395E-01 7.592242757921067E-02 +step = 75, x = 0.0280, y1and5 = -3.245175839317954E-01 -1.595988841404605E+00 +step = 77, x = 0.0290, y1and5 = -2.143416399470395E-01 -2.377050887803919E+00 +step = 79, x = 0.0300, y1and5 = -2.219528426584377E-02 -2.524853936017096E+00 +step = 80, x = 0.0310, y1and5 = 1.784315557745044E-01 -2.024353427269395E+00 +step = 82, x = 0.0320, y1and5 = 3.108403885437014E-01 -1.095033127297101E+00 +step = 84, x = 0.0330, y1and5 = 3.243889864399106E-01 -1.351281280093025E-01 +step = 86, x = 0.0340, y1and5 = 2.138589334227653E-01 4.561261316835924E-01 +step = 89, x = 0.0350, y1and5 = 2.177505545657471E-02 6.220899959378073E-01 +step = 90, x = 0.0360, y1and5 = -1.780029415728993E-01 6.219614121112279E-01 +step = 93, x = 0.0370, y1and5 = -3.096268589656677E-01 2.255018278055756E-01 +step = 99, x = 0.0380, y1and5 = -3.246033030835903E-01 -1.450729771018313E+00 +step = 102, x = 0.0390, y1and5 = -2.144090079930934E-01 -2.234602275910716E+00 +step = 103, x = 0.0400, y1and5 = -2.225575790074598E-02 -2.385080347010465E+00 +step = 104, x = 0.0410, y1and5 = 1.783880064630487E-01 -1.886779148717165E+00 +step = 106, x = 0.0420, y1and5 = 3.107983905954574E-01 -9.604321827007596E-01 +step = 108, x = 0.0430, y1and5 = 3.243575046789017E-01 -3.145824541993067E-03 +step = 111, x = 0.0440, y1and5 = 2.138294790088054E-01 5.856716223814071E-01 +step = 114, x = 0.0450, y1and5 = 2.175063895333042E-02 7.492442898711108E-01 +step = 115, x = 0.0460, y1and5 = -1.780245998980206E-01 7.466802611310924E-01 +step = 118, x = 0.0470, y1and5 = -3.096432354450164E-01 3.483520561277882E-01 +step = 123, x = 0.0480, y1and5 = -3.246172298547013E-01 -1.330381976389113E+00 +step = 126, x = 0.0490, y1and5 = -2.144228945666868E-01 -2.116620082962227E+00 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 1511 +Number of Jacobian evaluations = 126 +Number of factorizations = 166 +Number of lin sys solutions = 461 +Number of performed steps = 166 +Number of accepted steps = 127 +Number of rejected steps = 6 +Number of iterations (maximum) = 5 +y1to3 = -2.226517868073645E-02 3.068700099735197E+00 2.898340496450958E+00 +y4to5 = 2.033525366489690E+00 -2.269179823457655E+00 +h = 7.791381954171996E-04 diff --git a/russell_ode/data/fortran_radau5_hairer_wanner_eq1.txt b/russell_ode/data/fortran_radau5_hairer_wanner_eq1.txt new file mode 100644 index 00000000..ebd8b3d5 --- /dev/null +++ b/russell_ode/data/fortran_radau5_hairer_wanner_eq1.txt @@ -0,0 +1,21 @@ + +running radau5.f test +step = 0, x = 0.00, y = 0.000000000000000E+00 +step = 12, x = 0.20, y = 9.836063882718112E-01 +step = 14, x = 0.40, y = 9.284837278344403E-01 +step = 14, x = 0.60, y = 8.362817229180394E-01 +step = 15, x = 0.80, y = 7.107954474739064E-01 +step = 15, x = 1.00, y = 5.568498958591176E-01 +step = 15, x = 1.20, y = 3.807997101828213E-01 +step = 15, x = 1.40, y = 1.896857914831050E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 67 +Number of Jacobian evaluations = 1 +Number of factorizations = 13 +Number of lin sys solutions = 17 +Number of performed steps = 15 +Number of accepted steps = 15 +Number of rejected steps = 0 +Number of iterations (maximum) = 2 +y = 9.068021382386648E-02 +h = 1.272673814374611E+00 diff --git a/russell_ode/data/fortran_radau5_hairer_wanner_eq1_debug.txt b/russell_ode/data/fortran_radau5_hairer_wanner_eq1_debug.txt new file mode 100644 index 00000000..ac3ec1a6 --- /dev/null +++ b/russell_ode/data/fortran_radau5_hairer_wanner_eq1_debug.txt @@ -0,0 +1,30 @@ + +running radau5.f test +step = 1, newt = 1, ldw = 2.380787522481423E+01, h = 1.000000000000000E-04 +step = 1, newt = 2, ldw = 4.606206953146665E-15, h = 1.000000000000000E-04 +step = 2, newt = 1, ldw = 2.678969114879030E-04, h = 8.000000000000000E-04 +step = 3, newt = 1, ldw = 9.620335400378768E-01, h = 6.400000000000000E-03 +step = 4, newt = 1, ldw = 4.960011319658260E+00, h = 9.175315123155635E-03 +step = 5, newt = 1, ldw = 3.715067327320828E+00, h = 9.175315123155635E-03 +step = 6, newt = 1, ldw = 5.743754221374331E+00, h = 1.268482689579094E-02 +step = 7, newt = 1, ldw = 3.977586037047183E+00, h = 1.268482689579094E-02 +step = 8, newt = 1, ldw = 5.900855962632141E+00, h = 1.802519034151037E-02 +step = 9, newt = 1, ldw = 6.700127424360153E+00, h = 2.245393279005079E-02 +step = 9, newt = 2, ldw = 3.087212900082653E-14, h = 2.245393279005079E-02 +step = 10, newt = 1, ldw = 5.276818106903934E+00, h = 2.765273524751517E-02 +step = 11, newt = 1, ldw = 5.605282816310496E+00, h = 4.104314881966715E-02 +step = 12, newt = 1, ldw = 5.065318111735999E+00, h = 6.502285859141516E-02 +step = 13, newt = 1, ldw = 3.636739773459916E+00, h = 1.297895101231466E-01 +step = 14, newt = 1, ldw = 5.652222379590626E-01, h = 4.206510426488529E-01 +step = 15, newt = 1, ldw = 2.691177218158024E+01, h = 7.243412973999487E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 67 +Number of Jacobian evaluations = 1 +Number of factorizations = 13 +Number of lin sys solutions = 17 +Number of performed steps = 15 +Number of accepted steps = 15 +Number of rejected steps = 0 +Number of iterations (maximum) = 2 +y = 9.068021382386648E-02 +h = 1.272673814374611E+00 diff --git a/russell_ode/data/fortran_radau5_robertson.txt b/russell_ode/data/fortran_radau5_robertson.txt new file mode 100644 index 00000000..2e1f0cc1 --- /dev/null +++ b/russell_ode/data/fortran_radau5_robertson.txt @@ -0,0 +1,29 @@ + +running radau5.f test +step = 0, x = 0.00, y = 1.000000000000000E+00 0.000000000000000E+00 0.000000000000000E+00 +step = 1, x = 0.00, y = 9.999999600000008E-01 3.999998320000048E-08 1.599999952000000E-14 +step = 2, x = 0.00, y = 9.999996400000648E-01 3.599882716565646E-07 1.166354336752796E-11 +step = 3, x = 0.00, y = 9.999970800042659E-01 2.913787382334434E-06 6.208351820680937E-09 +step = 4, x = 0.00, y = 9.999863148909263E-01 1.307919521098008E-05 6.059138628105538E-07 +step = 5, x = 0.00, y = 9.999755499712033E-01 2.135889538415425E-05 3.091133412644612E-06 +step = 6, x = 0.00, y = 9.999647854413735E-01 2.724901208916897E-05 7.965546537469537E-06 +step = 7, x = 0.00, y = 9.999478453625211E-01 3.255484201352810E-05 1.959979546541964E-05 +step = 8, x = 0.00, y = 9.999253573022412E-01 3.530902486682297E-05 3.933367289202690E-05 +step = 9, x = 0.00, y = 9.999028742076045E-01 3.614958793293454E-05 6.097620446265927E-05 +step = 10, x = 0.00, y = 9.998490892191750E-01 3.647645985151893E-05 1.144343209735216E-04 +step = 11, x = 0.01, y = 9.997567898163745E-01 3.647881802650697E-05 2.067313655991199E-04 +step = 12, x = 0.01, y = 9.995023134527544E-01 3.643227719665359E-05 4.612542700490739E-04 +step = 13, x = 0.05, y = 9.982091577136255E-01 3.619191783743737E-05 1.754650368537187E-03 +step = 14, x = 0.18, y = 9.930472117083733E-01 3.525604364519484E-05 6.917532247981698E-03 +step = 15, x = 0.30, y = 9.886740138499884E-01 3.447720471782070E-05 1.129150894529390E-02 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 88 +Number of Jacobian evaluations = 8 +Number of factorizations = 15 +Number of lin sys solutions = 24 +Number of performed steps = 17 +Number of accepted steps = 15 +Number of rejected steps = 1 +Number of iterations (maximum) = 2 +y = 9.886740138499884E-01 3.447720471782070E-05 1.129150894529390E-02 +h = 8.160578540333708E-01 diff --git a/russell_ode/data/fortran_radau5_robertson_debug.txt b/russell_ode/data/fortran_radau5_robertson_debug.txt new file mode 100644 index 00000000..e86d42d7 --- /dev/null +++ b/russell_ode/data/fortran_radau5_robertson_debug.txt @@ -0,0 +1,37 @@ + +running radau5.f test +step = 1, newt = 1, ldw = 5.110074806379854E+00, h = 1.000000000000000E-06 +step = 1, newt = 2, ldw = 1.225505168722402E-06, h = 1.000000000000000E-06 +step = 2, newt = 1, ldw = 4.008140039601735E-08, h = 8.000000000000000E-06 +step = 3, newt = 1, ldw = 1.186679520173405E-03, h = 6.400000000000000E-05 +step = 4, newt = 1, ldw = 1.050182079832908E+01, h = 4.041220301432310E-04 +step = 5, newt = 1, ldw = 1.794588455309548E+00, h = 2.691302105638214E-04 +step = 6, newt = 1, ldw = 4.425316732018962E+00, h = 2.691302105638214E-04 +step = 7, newt = 1, ldw = 1.333759092929216E+00, h = 2.691302105638214E-04 +step = 8, newt = 1, ldw = 4.213092607065383E-01, h = 4.235640953755723E-04 +step = 9, newt = 1, ldw = 1.443689406814354E+01, h = 1.022273799207524E-03 +step = 9, newt = 2, ldw = 7.697798879909175E+00, h = 1.022273799207524E-03 +step = 10, newt = 1, ldw = 1.816083490438694E+00, h = 5.623781872720214E-04 +step = 10, newt = 2, ldw = 3.383615888638754E-02, h = 5.623781872720214E-04 +step = 11, newt = 1, ldw = 6.879082975344524E-01, h = 5.623781872720214E-04 +step = 12, newt = 1, ldw = 2.778605634164393E+00, h = 1.345864838744746E-03 +step = 12, newt = 2, ldw = 6.941901766125089E-02, h = 1.345864838744746E-03 +step = 13, newt = 1, ldw = 3.148269813802456E+00, h = 2.311325748596891E-03 +step = 13, newt = 2, ldw = 5.344836596772803E-02, h = 2.311325748596891E-03 +step = 14, newt = 1, ldw = 1.694875050694057E+00, h = 6.383707167707780E-03 +step = 14, newt = 2, ldw = 2.134263187226790E-02, h = 6.383707167707780E-03 +step = 15, newt = 1, ldw = 6.577883260391179E-01, h = 3.269640450706245E-02 +step = 16, newt = 1, ldw = 1.245612221344287E+00, h = 1.348771470980306E-01 +step = 16, newt = 2, ldw = 5.044276431150335E-02, h = 1.348771470980306E-01 +step = 17, newt = 1, ldw = 7.574602060940736E-02, h = 1.199568395382465E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 88 +Number of Jacobian evaluations = 8 +Number of factorizations = 15 +Number of lin sys solutions = 24 +Number of performed steps = 17 +Number of accepted steps = 15 +Number of rejected steps = 1 +Number of iterations (maximum) = 2 +y = 9.886740138499884E-01 3.447720471782070E-05 1.129150894529390E-02 +h = 8.160578540333708E-01 diff --git a/russell_ode/data/fortran_radau5_robertson_small_h.txt b/russell_ode/data/fortran_radau5_robertson_small_h.txt new file mode 100644 index 00000000..3f43dc77 --- /dev/null +++ b/russell_ode/data/fortran_radau5_robertson_small_h.txt @@ -0,0 +1,173 @@ + +running radau5.f test +step = 1, newt = 1, ldw = 5.713237318584357E-06, h = 1.000000000000000E-06 +step = 2, newt = 1, ldw = 9.549126037412026E-10, h = 8.000000000000000E-06 +step = 3, newt = 1, ldw = 1.411225368093479E-09, h = 6.400000000000000E-05 +step = 4, newt = 1, ldw = 4.107820729177997E-05, h = 5.120000000000000E-04 +step = 5, newt = 1, ldw = 7.224709706504758E-01, h = 4.096000000000000E-03 +step = 5, newt = 2, ldw = 5.688537757868064E+01, h = 4.096000000000000E-03 +step = 6, newt = 1, ldw = 5.535184873124125E-03, h = 2.048000000000000E-03 +step = 7, newt = 1, ldw = 2.183312157030154E-01, h = 2.048000000000000E-03 +step = 7, newt = 2, ldw = 7.939700994471953E+00, h = 2.048000000000000E-03 +step = 8, newt = 1, ldw = 2.064385209638172E-02, h = 1.024000000000000E-03 +step = 9, newt = 1, ldw = 1.851306702909944E+00, h = 1.024000000000000E-03 +step = 9, newt = 2, ldw = 3.418799139260299E+02, h = 1.024000000000000E-03 +step = 10, newt = 1, ldw = 1.511872801837098E+00, h = 5.120000000000000E-04 +step = 10, newt = 2, ldw = 5.326319204855027E+02, h = 5.120000000000000E-04 +step = 11, newt = 1, ldw = 2.833630003431720E-02, h = 2.560000000000000E-04 +step = 12, newt = 1, ldw = 7.979474595973187E-01, h = 1.353638004698506E-04 +step = 12, newt = 2, ldw = 1.318080076252194E+01, h = 1.353638004698506E-04 +step = 13, newt = 1, ldw = 6.269531353017967E-01, h = 6.768190023492531E-05 +step = 13, newt = 2, ldw = 7.885583465270912E+00, h = 6.768190023492531E-05 +step = 14, newt = 1, ldw = 9.452759342115000E-02, h = 3.384095011746265E-05 +step = 14, newt = 2, ldw = 3.703456575081365E-02, h = 3.384095011746265E-05 +step = 15, newt = 1, ldw = 9.463250538414410E-02, h = 7.988264928689847E-06 +step = 15, newt = 2, ldw = 1.982352214305149E-02, h = 7.988264928689847E-06 +step = 16, newt = 1, ldw = 3.239954519684029E-01, h = 5.459710401864838E-06 +step = 16, newt = 2, ldw = 2.855394028537098E-01, h = 5.459710401864838E-06 +step = 17, newt = 1, ldw = 5.515365612136778E-02, h = 3.003522188684837E-06 +step = 17, newt = 2, ldw = 7.214712288998376E-03, h = 3.003522188684837E-06 +step = 18, newt = 1, ldw = 3.507451382571531E-01, h = 3.003522188684837E-06 +step = 18, newt = 2, ldw = 2.488057505052997E-01, h = 3.003522188684837E-06 +step = 19, newt = 1, ldw = 7.456625523051499E-02, h = 1.958863891378516E-06 +step = 19, newt = 2, ldw = 1.437519505685940E-02, h = 1.958863891378516E-06 +step = 20, newt = 1, ldw = 1.129088123582558E+00, h = 1.958863891378516E-06 +step = 20, newt = 2, ldw = 1.305205610034040E+00, h = 1.958863891378516E-06 +step = 21, newt = 1, ldw = 9.429573341040615E-02, h = 9.794319456892581E-07 +step = 21, newt = 2, ldw = 1.277908234480324E-02, h = 9.794319456892581E-07 +step = 22, newt = 1, ldw = 7.611003462011605E-01, h = 9.794319456892581E-07 +step = 22, newt = 2, ldw = 4.432618501215841E-01, h = 9.794319456892581E-07 +step = 23, newt = 1, ldw = 2.239261385144834E-01, h = 7.034057509052370E-07 +step = 23, newt = 2, ldw = 4.821643905194671E-02, h = 7.034057509052370E-07 +step = 24, newt = 1, ldw = 2.016291416784896E+00, h = 5.891577599495123E-07 +step = 24, newt = 2, ldw = 1.545030446347030E+00, h = 5.891577599495123E-07 +step = 25, newt = 1, ldw = 2.640301675290290E-01, h = 3.241103051985691E-07 +step = 25, newt = 2, ldw = 3.338633178101812E-02, h = 3.241103051985691E-07 +step = 26, newt = 1, ldw = 1.522350659042311E+00, h = 3.086307641305829E-07 +step = 26, newt = 2, ldw = 6.814228576856886E-01, h = 3.086307641305829E-07 +step = 26, newt = 3, ldw = 2.349473131292367E-01, h = 3.086307641305829E-07 +step = 26, newt = 4, ldw = 5.210823998913975E-02, h = 3.086307641305829E-07 +step = 27, newt = 1, ldw = 3.799990707411193E-01, h = 2.084878525660416E-07 +step = 27, newt = 2, ldw = 5.457815646921323E-02, h = 2.084878525660416E-07 +step = 28, newt = 1, ldw = 2.024286080740297E+00, h = 1.798336446621222E-07 +step = 28, newt = 2, ldw = 7.826846012544822E-01, h = 1.798336446621222E-07 +step = 28, newt = 3, ldw = 2.353500555462282E-01, h = 1.798336446621222E-07 +step = 28, newt = 4, ldw = 4.530280229605301E-02, h = 1.798336446621222E-07 +step = 29, newt = 1, ldw = 4.856578889289867E-01, h = 1.184950958849866E-07 +step = 29, newt = 2, ldw = 5.712671518936909E-02, h = 1.184950958849866E-07 +step = 30, newt = 1, ldw = 1.965058779880622E+00, h = 1.031979132088188E-07 +step = 30, newt = 2, ldw = 5.583839431231045E-01, h = 1.031979132088188E-07 +step = 30, newt = 3, ldw = 1.227945951527147E-01, h = 1.031979132088188E-07 +step = 30, newt = 4, ldw = 1.725700353445325E-02, h = 1.031979132088188E-07 +step = 31, newt = 1, ldw = 6.332650257972264E-01, h = 7.415329799060239E-08 +step = 31, newt = 2, ldw = 7.159185197571510E-02, h = 7.415329799060239E-08 +step = 32, newt = 1, ldw = 2.182971710365843E+00, h = 6.216749325826338E-08 +step = 32, newt = 2, ldw = 5.107019541988799E-01, h = 6.216749325826338E-08 +step = 32, newt = 3, ldw = 9.305787269147595E-02, h = 6.216749325826338E-08 +step = 33, newt = 1, ldw = 9.301678868117234E-01, h = 4.843179380746314E-08 +step = 33, newt = 2, ldw = 1.091900273414632E-01, h = 4.843179380746314E-08 +step = 34, newt = 1, ldw = 2.318363930692459E+00, h = 3.670453510387579E-08 +step = 34, newt = 2, ldw = 4.305895750078587E-01, h = 3.670453510387579E-08 +step = 34, newt = 3, ldw = 6.362288570137736E-02, h = 3.670453510387579E-08 +step = 35, newt = 1, ldw = 2.247998360604570E+00, h = 1.821592603516536E-08 +step = 35, newt = 2, ldw = 2.775654255497899E-01, h = 1.821592603516536E-08 +step = 35, newt = 3, ldw = 2.888189217893338E-02, h = 1.821592603516536E-08 +step = 36, newt = 1, ldw = 1.974934301938218E+00, h = 1.064267467483143E-08 +step = 36, newt = 2, ldw = 2.007230940773211E-01, h = 1.064267467483143E-08 +step = 37, newt = 1, ldw = 2.544263026665214E+00, h = 7.252120971213548E-09 +step = 37, newt = 2, ldw = 2.799854451453948E-01, h = 7.252120971213548E-09 +step = 37, newt = 3, ldw = 2.529667157039526E-02, h = 7.252120971213548E-09 +step = 38, newt = 1, ldw = 2.484914027510850E+00, h = 4.492688244087684E-09 +step = 38, newt = 2, ldw = 2.427564460395474E-01, h = 4.492688244087684E-09 +step = 39, newt = 1, ldw = 2.762475172054663E+00, h = 3.002198771243084E-09 +step = 39, newt = 2, ldw = 2.706279629982239E-01, h = 3.002198771243084E-09 +step = 40, newt = 1, ldw = 2.832273398000657E+00, h = 1.966022679406377E-09 +step = 40, newt = 2, ldw = 2.661076871356216E-01, h = 1.966022679406377E-09 +step = 41, newt = 1, ldw = 2.853974292080237E+00, h = 1.306256884917544E-09 +step = 41, newt = 2, ldw = 2.623089508887610E-01, h = 1.306256884917544E-09 +step = 42, newt = 1, ldw = 2.900221148544711E+00, h = 8.768185854279031E-10 +step = 42, newt = 2, ldw = 2.645550255945693E-01, h = 8.768185854279031E-10 +step = 43, newt = 1, ldw = 2.934290654569171E+00, h = 5.897332567146582E-10 +step = 43, newt = 2, ldw = 2.657094886185315E-01, h = 5.897332567146582E-10 +step = 44, newt = 1, ldw = 2.953465450781620E+00, h = 3.974627531186378E-10 +step = 44, newt = 2, ldw = 2.659039432931001E-01, h = 3.974627531186378E-10 +step = 45, newt = 1, ldw = 2.967376833073354E+00, h = 2.684253824106721E-10 +step = 45, newt = 2, ldw = 2.662287390418155E-01, h = 2.684253824106721E-10 +step = 46, newt = 1, ldw = 2.977634165887978E+00, h = 1.814947218386549E-10 +step = 46, newt = 2, ldw = 2.665336147108092E-01, h = 1.814947218386549E-10 +step = 47, newt = 1, ldw = 2.984551675772167E+00, h = 1.228071769278898E-10 +step = 47, newt = 2, ldw = 2.667153165467080E-01, h = 1.228071769278898E-10 +step = 48, newt = 1, ldw = 2.989280122848438E+00, h = 8.314080980760846E-11 +step = 48, newt = 2, ldw = 2.668369994801806E-01, h = 8.314080980760846E-11 +step = 49, newt = 1, ldw = 2.992669984273070E+00, h = 5.630670969181890E-11 +step = 49, newt = 2, ldw = 2.669269649891330E-01, h = 5.630670969181890E-11 +step = 50, newt = 1, ldw = 2.995292844073978E+00, h = 3.814200296721970E-11 +step = 50, newt = 2, ldw = 2.669914052700605E-01, h = 3.814200296721970E-11 +step = 51, newt = 1, ldw = 2.997810990259810E+00, h = 2.584044664393496E-11 +step = 51, newt = 2, ldw = 2.670434656886297E-01, h = 2.584044664393496E-11 +step = 52, newt = 1, ldw = 3.001360575312002E+00, h = 1.750657207722925E-11 +step = 52, newt = 2, ldw = 2.671084029477461E-01, h = 1.750657207722925E-11 +step = 53, newt = 1, ldw = 3.008648835564353E+00, h = 1.185832044460369E-11 +step = 53, newt = 2, ldw = 2.672585670643872E-01, h = 1.185832044460369E-11 +step = 54, newt = 1, ldw = 3.027838574045401E+00, h = 8.027174893536473E-12 +step = 54, newt = 2, ldw = 2.677764060377021E-01, h = 8.027174893536473E-12 +step = 55, newt = 1, ldw = 3.088821934694739E+00, h = 5.422524811173549E-12 +step = 55, newt = 2, ldw = 2.699543206915546E-01, h = 5.422524811173549E-12 +step = 56, newt = 1, ldw = 3.328302464383194E+00, h = 3.636640310811574E-12 +step = 56, newt = 2, ldw = 2.806136775272576E-01, h = 3.636640310811574E-12 +step = 57, newt = 1, ldw = 2.862338196072172E+00, h = 2.368371140003442E-12 +step = 57, newt = 2, ldw = 2.124778328093391E-01, h = 2.368371140003442E-12 +step = 58, newt = 1, ldw = 3.019918842202491E+00, h = 1.708696809930333E-12 +step = 58, newt = 2, ldw = 2.436480743437542E-01, h = 1.708696809930333E-12 +step = 59, newt = 1, ldw = 3.165015855817321E+00, h = 1.188301059887269E-12 +step = 59, newt = 2, ldw = 2.592082896010620E-01, h = 1.188301059887269E-12 +step = 60, newt = 1, ldw = 3.137748677178956E+00, h = 8.144089923077925E-13 +step = 60, newt = 2, ldw = 2.536406296256570E-01, h = 8.144089923077925E-13 +step = 61, newt = 1, ldw = 3.123456848139974E+00, h = 5.618734579145754E-13 +step = 61, newt = 2, ldw = 2.524646503416266E-01, h = 5.618734579145754E-13 +step = 62, newt = 1, ldw = 3.128709160780355E+00, h = 3.879922829194020E-13 +step = 62, newt = 2, ldw = 2.532954494340044E-01, h = 3.879922829194020E-13 +step = 63, newt = 1, ldw = 3.129766416817052E+00, h = 2.676725339605763E-13 +step = 63, newt = 2, ldw = 2.533279713103022E-01, h = 2.676725339605763E-13 +step = 64, newt = 1, ldw = 3.128995216959003E+00, h = 1.846680970652057E-13 +step = 64, newt = 2, ldw = 2.532222899653750E-01, h = 1.846680970652057E-13 +step = 65, newt = 1, ldw = 3.128977990148663E+00, h = 1.274173745839448E-13 +step = 65, newt = 2, ldw = 2.532326447026258E-01, h = 1.274173745839448E-13 +step = 66, newt = 1, ldw = 3.129080262268820E+00, h = 8.791404984166864E-14 +step = 66, newt = 2, ldw = 2.532442627194222E-01, h = 8.791404984166864E-14 +step = 67, newt = 1, ldw = 3.129072668136896E+00, h = 6.065730744795438E-14 +step = 67, newt = 2, ldw = 2.532415141789461E-01, h = 6.065730744795438E-14 +step = 68, newt = 1, ldw = 3.129063972922062E+00, h = 4.185136430614088E-14 +step = 68, newt = 2, ldw = 2.532405151537980E-01, h = 4.185136430614088E-14 +step = 69, newt = 1, ldw = 3.129067978300279E+00, h = 2.887596914831532E-14 +step = 69, newt = 2, ldw = 2.532410196357864E-01, h = 2.887596914831532E-14 +step = 70, newt = 1, ldw = 3.129069822180670E+00, h = 1.992339383395278E-14 +step = 70, newt = 2, ldw = 2.532410980893514E-01, h = 1.992339383395278E-14 +step = 71, newt = 1, ldw = 3.129069996401301E+00, h = 1.374643509878522E-14 +step = 71, newt = 2, ldw = 2.532410426436404E-01, h = 1.374643509878522E-14 +step = 72, newt = 1, ldw = 3.129070376127181E+00, h = 9.484553811443425E-15 +step = 72, newt = 2, ldw = 2.532410519957973E-01, h = 9.484553811443425E-15 +step = 73, newt = 1, ldw = 3.129070737373610E+00, h = 6.544006710779086E-15 +step = 73, newt = 2, ldw = 2.532410654413833E-01, h = 6.544006710779086E-15 +step = 74, newt = 1, ldw = 3.129070941638575E+00, h = 4.515133270921864E-15 +step = 74, newt = 2, ldw = 2.532410679690361E-01, h = 4.515133270921864E-15 +step = 75, newt = 1, ldw = 3.129071076495257E+00, h = 3.115282382403675E-15 +step = 75, newt = 2, ldw = 2.532410697645263E-01, h = 3.115282382403675E-15 +step = 76, newt = 1, ldw = 3.129071175851432E+00, h = 2.149434768446396E-15 +step = 76, newt = 2, ldw = 2.532410718204264E-01, h = 2.149434768446396E-15 +step = 77, newt = 1, ldw = 3.129071244302047E+00, h = 1.483034052913010E-15 +step = 77, newt = 2, ldw = 2.532410731231322E-01, h = 1.483034052913010E-15 +step = 78, newt = 1, ldw = 3.129071290775475E+00, h = 1.023241107773565E-15 +step = 78, newt = 2, ldw = 2.532410739377414E-01, h = 1.023241107773565E-15 +ERROR: THE STEPSIZE BECOMES TOO SMALL +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 542 +Number of Jacobian evaluations = 61 +Number of factorizations = 77 +Number of lin sys solutions = 159 +Number of performed steps = 79 +Number of accepted steps = 63 +Number of rejected steps = 4 +Number of iterations (maximum) = 4 +y = -4.883696833176666E+03 -1.464431899711471E+07 1.464920369394789E+07 +h = 7.060002205795461E-16 diff --git a/russell_ode/data/fortran_radau5_van_der_pol.txt b/russell_ode/data/fortran_radau5_van_der_pol.txt new file mode 100644 index 00000000..b5206b7a --- /dev/null +++ b/russell_ode/data/fortran_radau5_van_der_pol.txt @@ -0,0 +1,24 @@ + +running radau5.f test +step = 0, x = 0.00, y = 2.000000000000000E+00 -6.000000000000000E-01 +step = 14, x = 0.20, y = 1.858193270854634E+00 -7.575101255757266E-01 +step = 15, x = 0.40, y = 1.693203123722811E+00 -9.069127086067813E-01 +step = 17, x = 0.60, y = 1.484568811304380E+00 -1.233096041433404E+00 +step = 26, x = 0.80, y = 1.083920681222190E+00 -6.195489374898189E+00 +step = 127, x = 1.00, y = -1.863647684640773E+00 7.535336997436557E-01 +step = 128, x = 1.20, y = -1.699726525700287E+00 8.997803216816892E-01 +step = 130, x = 1.40, y = -1.493380180170860E+00 1.213868996692375E+00 +step = 137, x = 1.60, y = -1.120792932708670E+00 4.374425849429084E+00 +step = 241, x = 1.80, y = 1.869055755232597E+00 -7.495776558060578E-01 +step = 242, x = 2.00, y = 1.706163410178080E+00 -8.927971289301173E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 2248 +Number of Jacobian evaluations = 162 +Number of factorizations = 252 +Number of lin sys solutions = 668 +Number of performed steps = 280 +Number of accepted steps = 242 +Number of rejected steps = 8 +Number of iterations (maximum) = 6 +y = 1.706163410178079E+00 -8.927971289301175E-01 +h = 1.510987221365367E-01 diff --git a/russell_ode/data/fortran_radau5_van_der_pol_debug.txt b/russell_ode/data/fortran_radau5_van_der_pol_debug.txt new file mode 100644 index 00000000..b9491855 --- /dev/null +++ b/russell_ode/data/fortran_radau5_van_der_pol_debug.txt @@ -0,0 +1,681 @@ + +running radau5.f test +step = 1, newt = 1, ldw = 2.436352665990826E+02, h = 1.000000000000000E-06 +step = 1, newt = 2, ldw = 3.880290086635195E-05, h = 1.000000000000000E-06 +step = 2, newt = 1, ldw = 3.935169421326134E+01, h = 1.000000000000000E-07 +step = 3, newt = 1, ldw = 5.065177021861066E-02, h = 1.000000000000000E-07 +step = 4, newt = 1, ldw = 3.222282661742849E+00, h = 3.885735108902087E-07 +step = 5, newt = 1, ldw = 6.519580022026556E+00, h = 4.972810029441471E-07 +step = 6, newt = 1, ldw = 5.807917778679836E+00, h = 7.083509253535920E-07 +step = 6, newt = 2, ldw = 3.209234642843608E-06, h = 7.083509253535920E-07 +step = 7, newt = 1, ldw = 4.089966344088056E+00, h = 1.073623851057028E-06 +step = 8, newt = 1, ldw = 3.364871108710838E+00, h = 2.290473452675524E-06 +step = 9, newt = 1, ldw = 1.700114059461571E+00, h = 7.536689064027484E-06 +step = 10, newt = 1, ldw = 6.970135373330144E-01, h = 4.422664055466360E-05 +step = 11, newt = 1, ldw = 9.017945175862685E-02, h = 3.538131244373088E-04 +step = 12, newt = 1, ldw = 1.629341581234408E-01, h = 2.830504995498470E-03 +step = 13, newt = 1, ldw = 7.271934629915553E-01, h = 2.264403996398776E-02 +step = 13, newt = 2, ldw = 1.578498073930680E-02, h = 2.264403996398776E-02 +step = 14, newt = 1, ldw = 3.737282766517767E-01, h = 1.059258245281148E-01 +step = 14, newt = 2, ldw = 3.422760762883785E-02, h = 1.059258245281148E-01 +step = 15, newt = 1, ldw = 2.623588145602348E+01, h = 4.141772841000433E-01 +step = 15, newt = 2, ldw = 1.488865811874123E+01, h = 4.141772841000433E-01 +step = 16, newt = 1, ldw = 3.132626174088666E+00, h = 2.278492028476093E-01 +step = 16, newt = 2, ldw = 8.767206658474501E-01, h = 2.278492028476093E-01 +step = 16, newt = 3, ldw = 2.408783656026472E-01, h = 2.278492028476093E-01 +step = 16, newt = 4, ldw = 6.510927800248559E-02, h = 2.278492028476093E-01 +step = 16, newt = 5, ldw = 1.726194459938509E-02, h = 2.278492028476093E-01 +step = 17, newt = 1, ldw = 2.601024839490872E+01, h = 2.278492028476093E-01 +step = 17, newt = 2, ldw = 1.026076152718142E+01, h = 2.278492028476093E-01 +step = 18, newt = 1, ldw = 7.069478522572002E+00, h = 1.350257782493808E-01 +step = 18, newt = 2, ldw = 1.377857661327136E+00, h = 1.350257782493808E-01 +step = 18, newt = 3, ldw = 3.167582456172106E-01, h = 1.350257782493808E-01 +step = 18, newt = 4, ldw = 7.448580328598077E-02, h = 1.350257782493808E-01 +step = 18, newt = 5, ldw = 1.721897842613337E-02, h = 1.350257782493808E-01 +step = 19, newt = 1, ldw = 1.643593740410093E+01, h = 1.350257782493808E-01 +step = 19, newt = 2, ldw = 5.288488097902941E+00, h = 1.350257782493808E-01 +step = 20, newt = 1, ldw = 7.550942582513166E+00, h = 1.001464452794707E-01 +step = 20, newt = 2, ldw = 1.626911072803231E+00, h = 1.001464452794707E-01 +step = 20, newt = 3, ldw = 3.936137909122376E-01, h = 1.001464452794707E-01 +step = 20, newt = 4, ldw = 9.611025390797522E-02, h = 1.001464452794707E-01 +step = 20, newt = 5, ldw = 2.301210121948871E-02, h = 1.001464452794707E-01 +step = 21, newt = 1, ldw = 2.475834989097885E+01, h = 1.001464452794707E-01 +step = 21, newt = 2, ldw = 8.718396823451434E+00, h = 1.001464452794707E-01 +step = 22, newt = 1, ldw = 8.390411043047294E+00, h = 6.557565230296628E-02 +step = 22, newt = 2, ldw = 1.654160471873837E+00, h = 6.557565230296628E-02 +step = 22, newt = 3, ldw = 3.725755229874785E-01, h = 6.557565230296628E-02 +step = 22, newt = 4, ldw = 8.510014557920888E-02, h = 6.557565230296628E-02 +step = 22, newt = 5, ldw = 1.906672198772326E-02, h = 6.557565230296628E-02 +step = 23, newt = 1, ldw = 2.257712890228188E+01, h = 6.557565230296628E-02 +step = 23, newt = 2, ldw = 7.368542241518287E+00, h = 6.557565230296628E-02 +step = 24, newt = 1, ldw = 9.091830670965420E+00, h = 4.620885633562398E-02 +step = 24, newt = 2, ldw = 1.834569787473722E+00, h = 4.620885633562398E-02 +step = 24, newt = 3, ldw = 4.144241481140580E-01, h = 4.620885633562398E-02 +step = 24, newt = 4, ldw = 9.445367126424903E-02, h = 4.620885633562398E-02 +step = 24, newt = 5, ldw = 2.109253745954136E-02, h = 4.620885633562398E-02 +step = 25, newt = 1, ldw = 2.726450871693790E+01, h = 4.620885633562398E-02 +step = 25, newt = 2, ldw = 9.111093916139486E+00, h = 4.620885633562398E-02 +step = 26, newt = 1, ldw = 9.873103539111510E+00, h = 3.119889966304367E-02 +step = 26, newt = 2, ldw = 1.911336300551397E+00, h = 3.119889966304367E-02 +step = 26, newt = 3, ldw = 4.157097723933866E-01, h = 3.119889966304367E-02 +step = 26, newt = 4, ldw = 9.131318035143028E-02, h = 3.119889966304367E-02 +step = 26, newt = 5, ldw = 1.964843890314926E-02, h = 3.119889966304367E-02 +step = 27, newt = 1, ldw = 2.748065753174600E+01, h = 3.119889966304367E-02 +step = 27, newt = 2, ldw = 8.871983691431902E+00, h = 3.119889966304367E-02 +step = 28, newt = 1, ldw = 1.059701044547153E+01, h = 2.164088586517934E-02 +step = 28, newt = 2, ldw = 2.052870811450118E+00, h = 2.164088586517934E-02 +step = 28, newt = 3, ldw = 4.424336853444109E-01, h = 2.164088586517934E-02 +step = 28, newt = 4, ldw = 9.606871883453848E-02, h = 2.164088586517934E-02 +step = 28, newt = 5, ldw = 2.042104358024061E-02, h = 2.164088586517934E-02 +step = 29, newt = 1, ldw = 2.905027030557912E+01, h = 2.125655690873182E-02 +step = 29, newt = 2, ldw = 9.175307287455096E+00, h = 2.125655690873182E-02 +step = 30, newt = 1, ldw = 1.154639899223995E+01, h = 1.490438830222701E-02 +step = 30, newt = 2, ldw = 2.213691888598177E+00, h = 1.490438830222701E-02 +step = 30, newt = 3, ldw = 4.710403708354109E-01, h = 1.490438830222701E-02 +step = 30, newt = 4, ldw = 1.009110953534241E-01, h = 1.490438830222701E-02 +step = 30, newt = 5, ldw = 2.115364150375437E-02, h = 1.490438830222701E-02 +step = 31, newt = 1, ldw = 2.962699383509554E+01, h = 1.433808980900640E-02 +step = 31, newt = 2, ldw = 9.060540935206715E+00, h = 1.433808980900640E-02 +step = 32, newt = 1, ldw = 1.253319869417675E+01, h = 1.029289966982484E-02 +step = 32, newt = 2, ldw = 2.398771038720433E+00, h = 1.029289966982484E-02 +step = 32, newt = 3, ldw = 5.075398132839335E-01, h = 1.029289966982484E-02 +step = 32, newt = 4, ldw = 1.079923289628639E-01, h = 1.029289966982484E-02 +step = 32, newt = 5, ldw = 2.247276729554187E-02, h = 1.029289966982484E-02 +step = 33, newt = 1, ldw = 3.012659859097154E+01, h = 9.638278768869487E-03 +step = 33, newt = 2, ldw = 8.915149808031476E+00, h = 9.638278768869487E-03 +step = 34, newt = 1, ldw = 1.361491187829855E+01, h = 7.089615164693940E-03 +step = 34, newt = 2, ldw = 2.603589675067120E+00, h = 7.089615164693940E-03 +step = 34, newt = 3, ldw = 5.490438508230430E-01, h = 7.089615164693940E-03 +step = 34, newt = 4, ldw = 1.163304452106642E-01, h = 7.089615164693940E-03 +step = 34, newt = 5, ldw = 2.409342361256548E-02, h = 7.089615164693940E-03 +step = 35, newt = 1, ldw = 3.054402442155346E+01, h = 6.461975503374752E-03 +step = 35, newt = 2, ldw = 8.761341591398244E+00, h = 6.461975503374752E-03 +step = 36, newt = 1, ldw = 1.470077384538114E+01, h = 4.865054591600743E-03 +step = 36, newt = 2, ldw = 2.810717739910687E+00, h = 4.865054591600743E-03 +step = 36, newt = 3, ldw = 5.914648943979532E-01, h = 4.865054591600743E-03 +step = 36, newt = 4, ldw = 1.249448167091104E-01, h = 4.865054591600743E-03 +step = 36, newt = 5, ldw = 2.578628859854914E-02, h = 4.865054591600743E-03 +step = 37, newt = 1, ldw = 3.079834091854903E+01, h = 4.318130751990487E-03 +step = 37, newt = 2, ldw = 8.568702891424715E+00, h = 4.318130751990487E-03 +step = 38, newt = 1, ldw = 1.578104295764491E+01, h = 3.327844350882229E-03 +step = 38, newt = 2, ldw = 3.020328164020657E+00, h = 3.327844350882229E-03 +step = 38, newt = 3, ldw = 6.350643563662893E-01, h = 3.327844350882229E-03 +step = 38, newt = 4, ldw = 1.339151038092411E-01, h = 3.327844350882229E-03 +step = 38, newt = 5, ldw = 2.757000853265217E-02, h = 3.327844350882229E-03 +step = 39, newt = 1, ldw = 3.085145627694470E+01, h = 2.873666383639927E-03 +step = 39, newt = 2, ldw = 8.317420671308598E+00, h = 2.873666383639927E-03 +step = 40, newt = 1, ldw = 1.688547714737962E+01, h = 2.270444414043001E-03 +step = 40, newt = 2, ldw = 3.239789446341947E+00, h = 2.270444414043001E-03 +step = 40, newt = 3, ldw = 6.816447523854234E-01, h = 2.270444414043001E-03 +step = 40, newt = 4, ldw = 1.436446663388436E-01, h = 2.270444414043001E-03 +step = 40, newt = 5, ldw = 2.952750121129763E-02, h = 2.270444414043001E-03 +step = 41, newt = 1, ldw = 3.061983863372626E+01, h = 1.901235361050597E-03 +step = 41, newt = 2, ldw = 7.967550632587112E+00, h = 1.901235361050597E-03 +step = 41, newt = 3, ldw = 2.177465033488136E+00, h = 1.901235361050597E-03 +step = 42, newt = 1, ldw = 1.722763461238833E+01, h = 1.516052377734619E-03 +step = 42, newt = 2, ldw = 3.227344567815824E+00, h = 1.516052377734619E-03 +step = 42, newt = 3, ldw = 6.642164071729929E-01, h = 1.516052377734619E-03 +step = 42, newt = 4, ldw = 1.368311096646098E-01, h = 1.516052377734619E-03 +step = 42, newt = 5, ldw = 2.746632863194757E-02, h = 1.516052377734619E-03 +step = 43, newt = 1, ldw = 2.904804302455895E+01, h = 1.255403977527396E-03 +step = 43, newt = 2, ldw = 7.246196025992711E+00, h = 1.255403977527396E-03 +step = 43, newt = 3, ldw = 1.896773958241477E+00, h = 1.255403977527396E-03 +step = 43, newt = 4, ldw = 4.896346736597764E-01, h = 1.255403977527396E-03 +step = 43, newt = 5, ldw = 1.225768979834001E-01, h = 1.255403977527396E-03 +step = 43, newt = 6, ldw = 2.993839801669795E-02, h = 1.255403977527396E-03 +step = 44, newt = 1, ldw = 4.004542834109020E+01, h = 7.727582149764797E-04 +step = 44, newt = 2, ldw = 9.416373374021369E+00, h = 7.727582149764797E-04 +step = 44, newt = 3, ldw = 2.448548215348005E+00, h = 7.727582149764797E-04 +step = 44, newt = 4, ldw = 6.302613353677224E-01, h = 7.727582149764797E-04 +step = 45, newt = 1, ldw = 2.387795197810256E+01, h = 6.178927302068291E-04 +step = 45, newt = 2, ldw = 4.005728978159361E+00, h = 6.178927302068291E-04 +step = 45, newt = 3, ldw = 7.782594494431109E-01, h = 6.178927302068291E-04 +step = 45, newt = 4, ldw = 1.519376809696055E-01, h = 6.178927302068291E-04 +step = 45, newt = 5, ldw = 2.871540188667606E-02, h = 6.178927302068291E-04 +step = 46, newt = 1, ldw = 1.659572520905737E+01, h = 4.140194770663144E-04 +step = 46, newt = 2, ldw = 2.828814944777923E+00, h = 4.140194770663144E-04 +step = 46, newt = 3, ldw = 5.216049349193315E-01, h = 4.140194770663144E-04 +step = 46, newt = 4, ldw = 9.454062232639597E-02, h = 4.140194770663144E-04 +step = 46, newt = 5, ldw = 1.645472371022969E-02, h = 4.140194770663144E-04 +step = 47, newt = 1, ldw = 2.185552697369243E+01, h = 3.240566068560727E-04 +step = 47, newt = 2, ldw = 4.354772372511712E+00, h = 3.240566068560727E-04 +step = 47, newt = 3, ldw = 8.949737628054658E-01, h = 3.240566068560727E-04 +step = 47, newt = 4, ldw = 1.769928888273968E-01, h = 3.240566068560727E-04 +step = 47, newt = 5, ldw = 3.321951332253902E-02, h = 3.240566068560727E-04 +step = 48, newt = 1, ldw = 2.376831420700163E+01, h = 2.098105164424190E-04 +step = 48, newt = 2, ldw = 4.150372212254130E+00, h = 2.098105164424190E-04 +step = 48, newt = 3, ldw = 7.510955321027828E-01, h = 2.098105164424190E-04 +step = 48, newt = 4, ldw = 1.275754974574497E-01, h = 2.098105164424190E-04 +step = 48, newt = 5, ldw = 2.004728377272411E-02, h = 2.098105164424190E-04 +step = 49, newt = 1, ldw = 1.386294118098933E+01, h = 1.190103110314773E-04 +step = 49, newt = 2, ldw = 1.594061310303836E+00, h = 1.190103110314773E-04 +step = 49, newt = 3, ldw = 1.834475606812070E-01, h = 1.190103110314773E-04 +step = 49, newt = 4, ldw = 1.882195323701152E-02, h = 1.190103110314773E-04 +step = 50, newt = 1, ldw = 7.562679055761329E+00, h = 7.846458712262406E-05 +step = 50, newt = 2, ldw = 6.239135087470519E-01, h = 7.846458712262406E-05 +step = 50, newt = 3, ldw = 4.685549503907453E-02, h = 7.846458712262406E-05 +step = 51, newt = 1, ldw = 6.670602912665365E+00, h = 6.171837503523898E-05 +step = 51, newt = 2, ldw = 4.811681376674983E-01, h = 6.171837503523898E-05 +step = 51, newt = 3, ldw = 2.916639732783106E-02, h = 6.171837503523898E-05 +step = 52, newt = 1, ldw = 6.581789033785904E+00, h = 4.820594813883537E-05 +step = 52, newt = 2, ldw = 3.896353051009582E-01, h = 4.820594813883537E-05 +step = 52, newt = 3, ldw = 1.850784433674546E-02, h = 4.820594813883537E-05 +step = 53, newt = 1, ldw = 5.961256320455365E+00, h = 3.720304896633610E-05 +step = 53, newt = 2, ldw = 2.750732636417901E-01, h = 3.720304896633610E-05 +step = 54, newt = 1, ldw = 5.709503845623584E+00, h = 2.983153309825810E-05 +step = 54, newt = 2, ldw = 2.158410697129158E-01, h = 2.983153309825810E-05 +step = 55, newt = 1, ldw = 5.518461596580739E+00, h = 2.430479245561035E-05 +step = 55, newt = 2, ldw = 1.758543984423691E-01, h = 2.430479245561035E-05 +step = 56, newt = 1, ldw = 5.412647347536608E+00, h = 2.007826328023059E-05 +step = 56, newt = 2, ldw = 1.487616876063931E-01, h = 2.007826328023059E-05 +step = 57, newt = 1, ldw = 5.353371396891045E+00, h = 1.676635423284854E-05 +step = 57, newt = 2, ldw = 1.295314357763403E-01, h = 1.676635423284854E-05 +step = 58, newt = 1, ldw = 5.318937831649325E+00, h = 1.412635306382137E-05 +step = 58, newt = 2, ldw = 1.154740103649255E-01, h = 1.412635306382137E-05 +step = 59, newt = 1, ldw = 5.300639051504696E+00, h = 1.199261803027345E-05 +step = 59, newt = 2, ldw = 1.050590319245199E-01, h = 1.199261803027345E-05 +step = 60, newt = 1, ldw = 5.292639913433281E+00, h = 1.024596527009617E-05 +step = 60, newt = 2, ldw = 9.724515671359411E-02, h = 1.024596527009617E-05 +step = 61, newt = 1, ldw = 5.290815710611804E+00, h = 8.800122416421716E-06 +step = 61, newt = 2, ldw = 9.131847754568492E-02, h = 8.800122416421716E-06 +step = 62, newt = 1, ldw = 5.292584566048825E+00, h = 7.591662503132144E-06 +step = 62, newt = 2, ldw = 8.678614097121559E-02, h = 7.591662503132144E-06 +step = 63, newt = 1, ldw = 5.296303677565462E+00, h = 6.573127944875293E-06 +step = 63, newt = 2, ldw = 8.329743620041678E-02, h = 6.573127944875293E-06 +step = 64, newt = 1, ldw = 5.300918409646229E+00, h = 5.708488991569862E-06 +step = 64, newt = 2, ldw = 8.059730062323346E-02, h = 5.708488991569862E-06 +step = 65, newt = 1, ldw = 5.305771642271413E+00, h = 4.970010248709789E-06 +step = 65, newt = 2, ldw = 7.849718703489407E-02, h = 4.970010248709789E-06 +step = 66, newt = 1, ldw = 5.310472398954723E+00, h = 4.336049905501034E-06 +step = 66, newt = 2, ldw = 7.685569892281452E-02, h = 4.336049905501034E-06 +step = 67, newt = 1, ldw = 5.314805869526579E+00, h = 3.789497294742423E-06 +step = 67, newt = 2, ldw = 7.556549889378743E-02, h = 3.789497294742423E-06 +step = 68, newt = 1, ldw = 5.318672066296302E+00, h = 3.316651175571864E-06 +step = 68, newt = 2, ldw = 7.454424918456405E-02, h = 3.316651175571864E-06 +step = 69, newt = 1, ldw = 5.322043584165074E+00, h = 2.906406627325480E-06 +step = 69, newt = 2, ldw = 7.372818670376421E-02, h = 2.906406627325480E-06 +step = 70, newt = 1, ldw = 5.324936635422880E+00, h = 2.549660397699692E-06 +step = 70, newt = 2, ldw = 7.306746163223017E-02, h = 2.549660397699692E-06 +step = 71, newt = 1, ldw = 5.327391431886471E+00, h = 2.238872153870339E-06 +step = 71, newt = 2, ldw = 7.252267814651332E-02, h = 2.238872153870339E-06 +step = 72, newt = 1, ldw = 5.329459122079177E+00, h = 1.967737767639913E-06 +step = 72, newt = 2, ldw = 7.206226502127203E-02, h = 1.967737767639913E-06 +step = 73, newt = 1, ldw = 5.331193238966604E+00, h = 1.730943626824512E-06 +step = 73, newt = 2, ldw = 7.166042008345269E-02, h = 1.730943626824512E-06 +step = 74, newt = 1, ldw = 5.332644113453817E+00, h = 1.523979940908918E-06 +step = 74, newt = 2, ldw = 7.129544249656176E-02, h = 1.523979940908918E-06 +step = 75, newt = 1, ldw = 5.333855030369009E+00, h = 1.342997339238995E-06 +step = 75, newt = 2, ldw = 7.094830534550907E-02, h = 1.342997339238995E-06 +step = 76, newt = 1, ldw = 5.334859059567210E+00, h = 1.184695564471864E-06 +step = 76, newt = 2, ldw = 7.060133434706170E-02, h = 1.184695564471864E-06 +step = 77, newt = 1, ldw = 5.335675453010600E+00, h = 1.046236304599779E-06 +step = 77, newt = 2, ldw = 7.023684611715553E-02, h = 1.046236304599779E-06 +step = 78, newt = 1, ldw = 5.336304139124736E+00, h = 9.251745848321543E-07 +step = 78, newt = 2, ldw = 6.983555079609488E-02, h = 9.251745848321543E-07 +step = 79, newt = 1, ldw = 5.336715885888126E+00, h = 8.194049670068406E-07 +step = 79, newt = 2, ldw = 6.937441174704731E-02, h = 8.194049670068406E-07 +step = 80, newt = 1, ldw = 5.336833434632624E+00, h = 7.271203615849082E-07 +step = 80, newt = 2, ldw = 6.882341354403683E-02, h = 7.271203615849082E-07 +step = 81, newt = 1, ldw = 5.336493508723724E+00, h = 6.467828928027284E-07 +step = 81, newt = 2, ldw = 6.814015746667598E-02, h = 6.467828928027284E-07 +step = 82, newt = 1, ldw = 5.335366021578068E+00, h = 5.771085780076486E-07 +step = 82, newt = 2, ldw = 6.725995750414357E-02, h = 5.771085780076486E-07 +step = 83, newt = 1, ldw = 5.332769641041025E+00, h = 5.170719900224651E-07 +step = 83, newt = 2, ldw = 6.607592536109044E-02, h = 5.170719900224651E-07 +step = 84, newt = 1, ldw = 5.327209198015521E+00, h = 4.659474741819023E-07 +step = 84, newt = 2, ldw = 6.439441631240243E-02, h = 4.659474741819023E-07 +step = 85, newt = 1, ldw = 5.315058395771963E+00, h = 4.234326619485186E-07 +step = 85, newt = 2, ldw = 6.182091482916388E-02, h = 4.234326619485186E-07 +step = 86, newt = 1, ldw = 5.286078481070580E+00, h = 3.899981086927264E-07 +step = 86, newt = 2, ldw = 5.740808591626717E-02, h = 3.899981086927264E-07 +step = 87, newt = 1, ldw = 5.203497596614675E+00, h = 3.680271079013824E-07 +step = 87, newt = 2, ldw = 4.822144872028870E-02, h = 3.680271079013824E-07 +step = 88, newt = 1, ldw = 4.498570279695978E+00, h = 3.577078930204004E-07 +step = 88, newt = 2, ldw = 1.830025709373691E-02, h = 3.577078930204004E-07 +step = 89, newt = 1, ldw = 2.230499239261958E+00, h = 3.899350502644419E-07 +step = 89, newt = 2, ldw = 1.136396422413771E-01, h = 3.899350502644419E-07 +step = 90, newt = 1, ldw = 9.231848045115216E+00, h = 3.005667111448801E-07 +step = 90, newt = 2, ldw = 3.799601971052241E-02, h = 3.005667111448801E-07 +step = 91, newt = 1, ldw = 3.749714235489794E+00, h = 2.284289467760068E-07 +step = 91, newt = 2, ldw = 4.134293199840085E-03, h = 2.284289467760068E-07 +step = 92, newt = 1, ldw = 2.890874299255476E+00, h = 1.832352747492499E-07 +step = 93, newt = 1, ldw = 3.154030480903093E+00, h = 1.832352747492499E-07 +step = 93, newt = 2, ldw = 8.238191583490947E-02, h = 1.832352747492499E-07 +step = 94, newt = 1, ldw = 4.418807057166680E+00, h = 2.069547815157841E-07 +step = 94, newt = 2, ldw = 1.676163356295099E-01, h = 2.069547815157841E-07 +step = 95, newt = 1, ldw = 6.027783913928706E+00, h = 2.581820378530671E-07 +step = 95, newt = 2, ldw = 8.940443796931688E-02, h = 2.581820378530671E-07 +step = 96, newt = 1, ldw = 1.726616278157523E+00, h = 1.882248732853600E-07 +step = 96, newt = 2, ldw = 3.051807868115986E-02, h = 1.882248732853600E-07 +step = 97, newt = 1, ldw = 4.765513561691355E+00, h = 1.609940375301117E-07 +step = 97, newt = 2, ldw = 9.068138524899252E-02, h = 1.609940375301117E-07 +step = 98, newt = 1, ldw = 5.300931343333120E+00, h = 1.592398406731782E-07 +step = 98, newt = 2, ldw = 1.079811500548546E-01, h = 1.592398406731782E-07 +step = 99, newt = 1, ldw = 4.073363274086904E+00, h = 1.862829851707231E-07 +step = 99, newt = 2, ldw = 5.651623976134603E-02, h = 1.862829851707231E-07 +step = 100, newt = 1, ldw = 1.790301196572665E+01, h = 2.509790591859371E-07 +step = 100, newt = 2, ldw = 9.889437225718997E-01, h = 2.509790591859371E-07 +step = 100, newt = 3, ldw = 2.825561118709366E-02, h = 2.509790591859371E-07 +step = 101, newt = 1, ldw = 4.328822388562090E+00, h = 1.659469354988921E-07 +step = 101, newt = 2, ldw = 1.198650469662222E-01, h = 1.659469354988921E-07 +step = 102, newt = 1, ldw = 3.938135671308891E+00, h = 1.291251266897515E-07 +step = 102, newt = 2, ldw = 3.941414346846087E-02, h = 1.291251266897515E-07 +step = 103, newt = 1, ldw = 6.587860698396663E+00, h = 1.660286818068464E-07 +step = 103, newt = 2, ldw = 6.713889052374998E-02, h = 1.660286818068464E-07 +step = 104, newt = 1, ldw = 7.083803946762653E+00, h = 1.807848158899085E-07 +step = 104, newt = 2, ldw = 3.356895882232528E-02, h = 1.807848158899085E-07 +step = 105, newt = 1, ldw = 2.015419995606260E+00, h = 2.347984261922148E-07 +step = 105, newt = 2, ldw = 9.885791391178772E-02, h = 2.347984261922148E-07 +step = 106, newt = 1, ldw = 2.121679862684012E+01, h = 2.406409710578856E-07 +step = 106, newt = 2, ldw = 2.081889882163848E-01, h = 2.406409710578856E-07 +step = 107, newt = 1, ldw = 1.031184220795229E+01, h = 1.901861338525830E-07 +step = 107, newt = 2, ldw = 6.928229375773026E-02, h = 1.901861338525830E-07 +step = 108, newt = 1, ldw = 7.664129937033677E+00, h = 1.547173182051051E-07 +step = 108, newt = 2, ldw = 1.704374423479928E-02, h = 1.547173182051051E-07 +step = 109, newt = 1, ldw = 9.338743126247527E+00, h = 1.668359497822658E-07 +step = 109, newt = 2, ldw = 1.428048889115944E-02, h = 1.668359497822658E-07 +step = 110, newt = 1, ldw = 1.127890072773359E+01, h = 1.665857157428450E-07 +step = 110, newt = 2, ldw = 1.016369432277364E-02, h = 1.665857157428450E-07 +step = 111, newt = 1, ldw = 1.134983485149754E+01, h = 1.632964854419280E-07 +step = 111, newt = 2, ldw = 1.592952180900218E-02, h = 1.632964854419280E-07 +step = 112, newt = 1, ldw = 1.106891424209300E+01, h = 1.610752550562738E-07 +step = 112, newt = 2, ldw = 3.433361023753960E-03, h = 1.610752550562738E-07 +step = 113, newt = 1, ldw = 1.089975269690306E+01, h = 1.598105625613784E-07 +step = 113, newt = 2, ldw = 5.440380143560342E-03, h = 1.598105625613784E-07 +step = 114, newt = 1, ldw = 1.079899049159656E+01, h = 1.590274846213183E-07 +step = 114, newt = 2, ldw = 6.666508630410143E-03, h = 1.590274846213183E-07 +step = 115, newt = 1, ldw = 1.073665048114661E+01, h = 1.585515001539910E-07 +step = 115, newt = 2, ldw = 7.411207267605690E-03, h = 1.585515001539910E-07 +step = 116, newt = 1, ldw = 1.069863709699278E+01, h = 1.582616338890510E-07 +step = 116, newt = 2, ldw = 7.867486463260281E-03, h = 1.582616338890510E-07 +step = 117, newt = 1, ldw = 1.067501383116531E+01, h = 1.580857603738980E-07 +step = 117, newt = 2, ldw = 8.148573372611070E-03, h = 1.580857603738980E-07 +step = 118, newt = 1, ldw = 1.065979367220542E+01, h = 1.579813570493632E-07 +step = 118, newt = 2, ldw = 8.322147625323986E-03, h = 1.579813570493632E-07 +step = 119, newt = 1, ldw = 1.064926772938417E+01, h = 1.579236724527724E-07 +step = 119, newt = 2, ldw = 8.429214380084357E-03, h = 1.579236724527724E-07 +step = 120, newt = 1, ldw = 1.064094484363947E+01, h = 1.578991804703117E-07 +step = 120, newt = 2, ldw = 8.494774979797786E-03, h = 1.578991804703117E-07 +step = 121, newt = 1, ldw = 1.063229850639383E+01, h = 1.578991804703117E-07 +step = 121, newt = 2, ldw = 8.533416604177948E-03, h = 1.578991804703117E-07 +step = 122, newt = 1, ldw = 1.061622377114303E+01, h = 1.578991804703117E-07 +step = 122, newt = 2, ldw = 8.548764898526870E-03, h = 1.578991804703117E-07 +step = 123, newt = 1, ldw = 1.058733535684437E+01, h = 1.578991804703117E-07 +step = 123, newt = 2, ldw = 8.542980634328214E-03, h = 1.578991804703117E-07 +step = 124, newt = 1, ldw = 1.053931245346396E+01, h = 1.578991804703117E-07 +step = 124, newt = 2, ldw = 8.515001000568310E-03, h = 1.578991804703117E-07 +step = 125, newt = 1, ldw = 1.046189490174347E+01, h = 1.578991804703117E-07 +step = 125, newt = 2, ldw = 8.459047412887066E-03, h = 1.578991804703117E-07 +step = 126, newt = 1, ldw = 1.033918810094545E+01, h = 1.578991804703117E-07 +step = 126, newt = 2, ldw = 8.363827595896151E-03, h = 1.578991804703117E-07 +step = 127, newt = 1, ldw = 1.014759934984929E+01, h = 1.578991804703117E-07 +step = 127, newt = 2, ldw = 8.211223941893922E-03, h = 1.578991804703117E-07 +step = 128, newt = 1, ldw = 9.854084459633434E+00, h = 1.578991804703117E-07 +step = 128, newt = 2, ldw = 7.975098384250926E-03, h = 1.578991804703117E-07 +step = 129, newt = 1, ldw = 9.416513172097090E+00, h = 1.578991804703117E-07 +step = 129, newt = 2, ldw = 7.621728500805910E-03, h = 1.578991804703117E-07 +step = 130, newt = 1, ldw = 8.789607823333052E+00, h = 1.578991804703117E-07 +step = 130, newt = 2, ldw = 7.114702932111156E-03, h = 1.578991804703117E-07 +step = 131, newt = 1, ldw = 7.940568120952881E+00, h = 1.578991804703117E-07 +step = 131, newt = 2, ldw = 6.427624829936346E-03, h = 1.578991804703117E-07 +step = 132, newt = 1, ldw = 6.054860484045599E+00, h = 1.578991804703117E-07 +step = 132, newt = 2, ldw = 4.901253104801813E-03, h = 1.578991804703117E-07 +step = 133, newt = 1, ldw = 3.105420586594002E+00, h = 1.578991804703117E-07 +step = 134, newt = 1, ldw = 5.398423188997671E+00, h = 2.289742877252299E-07 +step = 134, newt = 2, ldw = 6.077300864171981E-03, h = 2.289742877252299E-07 +step = 135, newt = 1, ldw = 5.413791013152022E+00, h = 2.590516448452221E-07 +step = 135, newt = 2, ldw = 1.572746664526550E-07, h = 2.590516448452221E-07 +step = 136, newt = 1, ldw = 5.160675474315800E+00, h = 3.193304711579980E-07 +step = 137, newt = 1, ldw = 6.167848836272821E+00, h = 4.326146609429487E-07 +step = 138, newt = 1, ldw = 6.126670158466093E+00, h = 5.883425741396023E-07 +step = 139, newt = 1, ldw = 5.420287321205315E+00, h = 8.849496714730589E-07 +step = 140, newt = 1, ldw = 4.259799021250090E+00, h = 1.585021904230345E-06 +step = 141, newt = 1, ldw = 2.527842174246181E+00, h = 4.023723327255933E-06 +step = 141, newt = 2, ldw = 1.336904511867162E-05, h = 4.023723327255933E-06 +step = 142, newt = 1, ldw = 8.212988120741281E-01, h = 1.774203245146005E-05 +step = 143, newt = 1, ldw = 4.671980042842874E-01, h = 1.413468175895420E-04 +step = 144, newt = 1, ldw = 7.399022365688418E-03, h = 1.130774540716336E-03 +step = 145, newt = 1, ldw = 4.148962115882724E-01, h = 9.046196325730690E-03 +step = 146, newt = 1, ldw = 4.102013414470661E+00, h = 7.236957060584552E-02 +step = 146, newt = 2, ldw = 2.840600350067993E-01, h = 7.236957060584552E-02 +step = 146, newt = 3, ldw = 2.262804904312868E-02, h = 7.236957060584552E-02 +step = 147, newt = 1, ldw = 5.458761082322174E-01, h = 1.902968026583101E-01 +step = 147, newt = 2, ldw = 1.237071747386432E-01, h = 1.902968026583101E-01 +step = 147, newt = 3, ldw = 2.532404231663630E-02, h = 1.902968026583101E-01 +step = 148, newt = 1, ldw = 3.567494824651926E+01, h = 3.270191529341175E-01 +step = 148, newt = 2, ldw = 1.897605754740694E+01, h = 3.270191529341175E-01 +step = 149, newt = 1, ldw = 6.219745797321789E+00, h = 1.799013518422251E-01 +step = 149, newt = 2, ldw = 1.519702221406438E+00, h = 1.799013518422251E-01 +step = 149, newt = 3, ldw = 4.062991325719950E-01, h = 1.799013518422251E-01 +step = 149, newt = 4, ldw = 1.088510709795048E-01, h = 1.799013518422251E-01 +step = 149, newt = 5, ldw = 2.858804998473801E-02, h = 1.799013518422251E-01 +step = 150, newt = 1, ldw = 2.751909815399710E+01, h = 1.799013518422251E-01 +step = 150, newt = 2, ldw = 1.075330005865185E+01, h = 1.799013518422251E-01 +step = 151, newt = 1, ldw = 7.463794445612183E+00, h = 1.067017522337539E-01 +step = 151, newt = 2, ldw = 1.433021795750513E+00, h = 1.067017522337539E-01 +step = 151, newt = 3, ldw = 3.232097390115152E-01, h = 1.067017522337539E-01 +step = 151, newt = 4, ldw = 7.448616808599749E-02, h = 1.067017522337539E-01 +step = 151, newt = 5, ldw = 1.686815463907277E-02, h = 1.067017522337539E-01 +step = 152, newt = 1, ldw = 1.730671787435509E+01, h = 1.067017522337539E-01 +step = 152, newt = 2, ldw = 5.507339135778635E+00, h = 1.067017522337539E-01 +step = 153, newt = 1, ldw = 7.977416571161464E+00, h = 7.933745334156968E-02 +step = 153, newt = 2, ldw = 1.701671447402482E+00, h = 7.933745334156968E-02 +step = 153, newt = 3, ldw = 4.052930755743520E-01, h = 7.933745334156968E-02 +step = 153, newt = 4, ldw = 9.730768936153660E-02, h = 7.933745334156968E-02 +step = 153, newt = 5, ldw = 2.290004174149923E-02, h = 7.933745334156968E-02 +step = 154, newt = 1, ldw = 2.625982788547872E+01, h = 7.933745334156968E-02 +step = 154, newt = 2, ldw = 9.175815058233651E+00, h = 7.933745334156968E-02 +step = 155, newt = 1, ldw = 8.856692199671746E+00, h = 5.189646320494705E-02 +step = 155, newt = 2, ldw = 1.724308281595992E+00, h = 5.189646320494705E-02 +step = 155, newt = 3, ldw = 3.822452051693062E-01, h = 5.189646320494705E-02 +step = 155, newt = 4, ldw = 8.586292399014839E-02, h = 5.189646320494705E-02 +step = 155, newt = 5, ldw = 1.891240175567121E-02, h = 5.189646320494705E-02 +step = 156, newt = 1, ldw = 2.368670817120137E+01, h = 5.189646320494705E-02 +step = 156, newt = 2, ldw = 7.657070603040538E+00, h = 5.189646320494705E-02 +step = 157, newt = 1, ldw = 9.564153522978597E+00, h = 3.663379497283138E-02 +step = 157, newt = 2, ldw = 1.911631338815489E+00, h = 3.663379497283138E-02 +step = 157, newt = 3, ldw = 4.262456044857632E-01, h = 3.663379497283138E-02 +step = 157, newt = 4, ldw = 9.581305111075746E-02, h = 3.663379497283138E-02 +step = 157, newt = 5, ldw = 2.109505861333599E-02, h = 3.663379497283138E-02 +step = 158, newt = 1, ldw = 2.832480902513826E+01, h = 3.649602304157668E-02 +step = 158, newt = 2, ldw = 9.354160244645557E+00, h = 3.649602304157668E-02 +step = 159, newt = 1, ldw = 1.037433081753142E+01, h = 2.476047811604664E-02 +step = 159, newt = 2, ldw = 1.993389632487402E+00, h = 2.476047811604664E-02 +step = 159, newt = 3, ldw = 4.290393369950859E-01, h = 2.476047811604664E-02 +step = 159, newt = 4, ldw = 9.318979187700062E-02, h = 2.476047811604664E-02 +step = 159, newt = 5, ldw = 1.982223196634509E-02, h = 2.476047811604664E-02 +step = 160, newt = 1, ldw = 2.885112054352162E+01, h = 2.476047811604664E-02 +step = 160, newt = 2, ldw = 9.276160501795289E+00, h = 2.476047811604664E-02 +step = 161, newt = 1, ldw = 1.103614532147162E+01, h = 1.712774187084369E-02 +step = 161, newt = 2, ldw = 2.116529258747146E+00, h = 1.712774187084369E-02 +step = 161, newt = 3, ldw = 4.507258000362634E-01, h = 1.712774187084369E-02 +step = 161, newt = 4, ldw = 9.666073228226388E-02, h = 1.712774187084369E-02 +step = 161, newt = 5, ldw = 2.028815372507231E-02, h = 1.712774187084369E-02 +step = 162, newt = 1, ldw = 2.927605495979586E+01, h = 1.667952294164777E-02 +step = 162, newt = 2, ldw = 9.063766756964494E+00, h = 1.667952294164777E-02 +step = 163, newt = 1, ldw = 1.209490973386484E+01, h = 1.187363287584971E-02 +step = 163, newt = 2, ldw = 2.318657524154325E+00, h = 1.187363287584971E-02 +step = 163, newt = 3, ldw = 4.916337335734591E-01, h = 1.187363287584971E-02 +step = 163, newt = 4, ldw = 1.048547404534702E-01, h = 1.187363287584971E-02 +step = 163, newt = 5, ldw = 2.187532569105689E-02, h = 1.187363287584971E-02 +step = 164, newt = 1, ldw = 2.996419968083992E+01, h = 1.123504929094753E-02 +step = 164, newt = 2, ldw = 8.980116666972620E+00, h = 1.123504929094753E-02 +step = 165, newt = 1, ldw = 1.319816349805537E+01, h = 8.186050301734801E-03 +step = 165, newt = 2, ldw = 2.524106592163014E+00, h = 8.186050301734801E-03 +step = 165, newt = 3, ldw = 5.328291377690558E-01, h = 8.186050301734801E-03 +step = 165, newt = 4, ldw = 1.130503923810710E-01, h = 8.186050301734801E-03 +step = 165, newt = 5, ldw = 2.345102685180232E-02, h = 8.186050301734801E-03 +step = 166, newt = 1, ldw = 3.042023373016354E+01, h = 7.541411704934831E-03 +step = 166, newt = 2, ldw = 8.833436188442564E+00, h = 7.541411704934831E-03 +step = 167, newt = 1, ldw = 1.427782062730769E+01, h = 5.625142315378657E-03 +step = 167, newt = 2, ldw = 2.729225937065489E+00, h = 5.625142315378657E-03 +step = 167, newt = 3, ldw = 5.746034201972454E-01, h = 5.625142315378657E-03 +step = 167, newt = 4, ldw = 1.214853388814335E-01, h = 5.625142315378657E-03 +step = 167, newt = 5, ldw = 2.509901711505830E-02, h = 5.625142315378657E-03 +step = 168, newt = 1, ldw = 3.071896219287162E+01, h = 5.045345415063581E-03 +step = 168, newt = 2, ldw = 8.648357690574796E+00, h = 5.045345415063581E-03 +step = 169, newt = 1, ldw = 1.536045806529553E+01, h = 3.852955918646649E-03 +step = 169, newt = 2, ldw = 2.938266750867443E+00, h = 3.852955918646649E-03 +step = 169, newt = 3, ldw = 6.178864866085539E-01, h = 3.852955918646649E-03 +step = 169, newt = 4, ldw = 1.303607737194065E-01, h = 3.852955918646649E-03 +step = 169, newt = 5, ldw = 2.685957859960792E-02, h = 3.852955918646649E-03 +step = 170, newt = 1, ldw = 3.085930854128609E+01, h = 3.363431023784686E-03 +step = 170, newt = 2, ldw = 8.423455761412717E+00, h = 3.363431023784686E-03 +step = 171, newt = 1, ldw = 1.645330162821280E+01, h = 2.631170792431946E-03 +step = 171, newt = 2, ldw = 3.153152783163425E+00, h = 2.631170792431946E-03 +step = 171, newt = 3, ldw = 6.631210594176772E-01, h = 2.631170792431946E-03 +step = 171, newt = 4, ldw = 1.397550512786631E-01, h = 2.631170792431946E-03 +step = 171, newt = 5, ldw = 2.874190216105679E-02, h = 2.631170792431946E-03 +step = 172, newt = 1, ldw = 3.075063647156196E+01, h = 2.230917915895408E-03 +step = 172, newt = 2, ldw = 8.117652139658858E+00, h = 2.230917915895408E-03 +step = 172, newt = 3, ldw = 2.247699044237216E+00, h = 2.230917915895408E-03 +step = 173, newt = 1, ldw = 1.672548421254400E+01, h = 1.755776276598617E-03 +step = 173, newt = 2, ldw = 3.121606644985238E+00, h = 1.755776276598617E-03 +step = 173, newt = 3, ldw = 6.408076282104690E-01, h = 1.755776276598617E-03 +step = 173, newt = 4, ldw = 1.317911854165817E-01, h = 1.755776276598617E-03 +step = 173, newt = 5, ldw = 2.642773247451234E-02, h = 1.755776276598617E-03 +step = 174, newt = 1, ldw = 2.933805145365733E+01, h = 1.480085695614891E-03 +step = 174, newt = 2, ldw = 7.462222122797622E+00, h = 1.480085695614891E-03 +step = 174, newt = 3, ldw = 1.987525546958562E+00, h = 1.480085695614891E-03 +step = 174, newt = 4, ldw = 5.221991952816964E-01, h = 1.480085695614891E-03 +step = 174, newt = 5, ldw = 1.331466487767821E-01, h = 1.480085695614891E-03 +step = 174, newt = 6, ldw = 3.314143218964674E-02, h = 1.480085695614891E-03 +step = 175, newt = 1, ldw = 4.150094962762947E+01, h = 9.053631113938078E-04 +step = 175, newt = 2, ldw = 9.931287834188867E+00, h = 9.053631113938078E-04 +step = 175, newt = 3, ldw = 2.637587763328886E+00, h = 9.053631113938078E-04 +step = 175, newt = 4, ldw = 6.948921120308993E-01, h = 9.053631113938078E-04 +step = 176, newt = 1, ldw = 2.332233714282365E+01, h = 7.034582773216228E-04 +step = 176, newt = 2, ldw = 3.819470016321859E+00, h = 7.034582773216228E-04 +step = 176, newt = 3, ldw = 7.314621960429230E-01, h = 7.034582773216228E-04 +step = 176, newt = 4, ldw = 1.414383797137644E-01, h = 7.034582773216228E-04 +step = 176, newt = 5, ldw = 2.653830102753990E-02, h = 7.034582773216228E-04 +step = 177, newt = 1, ldw = 1.665399739816434E+01, h = 4.887438783687799E-04 +step = 177, newt = 2, ldw = 2.940719050883656E+00, h = 4.887438783687799E-04 +step = 177, newt = 3, ldw = 5.599104308804709E-01, h = 4.887438783687799E-04 +step = 177, newt = 4, ldw = 1.051173790990765E-01, h = 4.887438783687799E-04 +step = 177, newt = 5, ldw = 1.901919300377807E-02, h = 4.887438783687799E-04 +step = 178, newt = 1, ldw = 2.460170209508368E+01, h = 3.899683565784574E-04 +step = 178, newt = 2, ldw = 5.270776714608660E+00, h = 3.899683565784574E-04 +step = 178, newt = 3, ldw = 1.168172516083333E+00, h = 3.899683565784574E-04 +step = 178, newt = 4, ldw = 2.505982677403795E-01, h = 3.899683565784574E-04 +step = 178, newt = 5, ldw = 5.130393089063908E-02, h = 3.899683565784574E-04 +step = 179, newt = 1, ldw = 2.721118780542966E+01, h = 2.436344628296559E-04 +step = 179, newt = 2, ldw = 5.027842434205207E+00, h = 2.436344628296559E-04 +step = 179, newt = 3, ldw = 9.820906320940447E-01, h = 2.436344628296559E-04 +step = 179, newt = 4, ldw = 1.822335991951689E-01, h = 2.436344628296559E-04 +step = 179, newt = 5, ldw = 3.157645536436637E-02, h = 2.436344628296559E-04 +step = 180, newt = 1, ldw = 1.503673142009564E+01, h = 1.329013743060113E-04 +step = 180, newt = 2, ldw = 1.809175035998469E+00, h = 1.329013743060113E-04 +step = 180, newt = 3, ldw = 2.238245087275015E-01, h = 1.329013743060113E-04 +step = 180, newt = 4, ldw = 2.510189397657850E-02, h = 1.329013743060113E-04 +step = 181, newt = 1, ldw = 7.853109446309472E+00, h = 8.661682076239578E-05 +step = 181, newt = 2, ldw = 6.886618313776059E-01, h = 8.661682076239578E-05 +step = 181, newt = 3, ldw = 5.628606733972654E-02, h = 8.661682076239578E-05 +step = 182, newt = 1, ldw = 7.165750248046317E+00, h = 6.895245887239832E-05 +step = 182, newt = 2, ldw = 5.733676797717666E-01, h = 6.895245887239832E-05 +step = 182, newt = 3, ldw = 3.924565774495261E-02, h = 6.895245887239832E-05 +step = 183, newt = 1, ldw = 7.055288976037039E+00, h = 5.306185533822556E-05 +step = 183, newt = 2, ldw = 4.582938840382244E-01, h = 5.306185533822556E-05 +step = 183, newt = 3, ldw = 2.431174483491292E-02, h = 5.306185533822556E-05 +step = 184, newt = 1, ldw = 6.103854188242063E+00, h = 4.011322993112586E-05 +step = 184, newt = 2, ldw = 3.004483266391239E-01, h = 4.011322993112586E-05 +step = 184, newt = 3, ldw = 1.159273046660520E-02, h = 4.011322993112586E-05 +step = 185, newt = 1, ldw = 5.558117412074536E+00, h = 3.180464689522362E-05 +step = 185, newt = 2, ldw = 2.214788435454061E-01, h = 3.180464689522362E-05 +step = 186, newt = 1, ldw = 5.579530275362504E+00, h = 2.608246243155363E-05 +step = 186, newt = 2, ldw = 1.891476660930657E-01, h = 2.608246243155363E-05 +step = 187, newt = 1, ldw = 5.472084453109226E+00, h = 2.140578538582810E-05 +step = 187, newt = 2, ldw = 1.580251013991047E-01, h = 2.140578538582810E-05 +step = 188, newt = 1, ldw = 5.368950024586498E+00, h = 1.778693283227849E-05 +step = 188, newt = 2, ldw = 1.351780217794747E-01, h = 1.778693283227849E-05 +step = 189, newt = 1, ldw = 5.325564685300423E+00, h = 1.494867990121517E-05 +step = 189, newt = 2, ldw = 1.196468548313476E-01, h = 1.494867990121517E-05 +step = 190, newt = 1, ldw = 5.305550740010585E+00, h = 1.266177437879301E-05 +step = 190, newt = 2, ldw = 1.082362456719588E-01, h = 1.266177437879301E-05 +step = 191, newt = 1, ldw = 5.294640901561473E+00, h = 1.079564129923582E-05 +step = 191, newt = 2, ldw = 9.963397190882776E-02, h = 1.079564129923582E-05 +step = 192, newt = 1, ldw = 5.290905004048354E+00, h = 9.256712963142847E-06 +step = 192, newt = 2, ldw = 9.313285616437196E-02, h = 9.256712963142847E-06 +step = 193, newt = 1, ldw = 5.291699214543809E+00, h = 7.974478536059335E-06 +step = 193, newt = 2, ldw = 8.817757465186964E-02, h = 7.974478536059335E-06 +step = 194, newt = 1, ldw = 5.294912489316178E+00, h = 6.896632988830536E-06 +step = 194, newt = 2, ldw = 8.437076474435040E-02, h = 6.896632988830536E-06 +step = 195, newt = 1, ldw = 5.299315082083134E+00, h = 5.983732036508905E-06 +step = 195, newt = 2, ldw = 8.142947373255580E-02, h = 5.983732036508905E-06 +step = 196, newt = 1, ldw = 5.304142666741926E+00, h = 5.205538364607589E-06 +step = 196, newt = 2, ldw = 7.914552600174224E-02, h = 5.205538364607589E-06 +step = 197, newt = 1, ldw = 5.308925929619940E+00, h = 4.538563083050189E-06 +step = 197, newt = 2, ldw = 7.736337060315904E-02, h = 4.538563083050189E-06 +step = 198, newt = 1, ldw = 5.313398656164005E+00, h = 3.964316718276720E-06 +step = 198, newt = 2, ldw = 7.596539806770335E-02, h = 3.964316718276720E-06 +step = 199, newt = 1, ldw = 5.317427831738932E+00, h = 3.468056188370374E-06 +step = 199, newt = 2, ldw = 7.486169638839638E-02, h = 3.468056188370374E-06 +step = 200, newt = 1, ldw = 5.320965458277382E+00, h = 3.037879787776946E-06 +step = 200, newt = 2, ldw = 7.398285433174524E-02, h = 3.037879787776946E-06 +step = 201, newt = 1, ldw = 5.324015676420983E+00, h = 2.664067262214142E-06 +step = 201, newt = 2, ldw = 7.327478316894558E-02, h = 2.664067262214142E-06 +step = 202, newt = 1, ldw = 5.326612368149953E+00, h = 2.338594347281745E-06 +step = 202, newt = 2, ldw = 7.269490251983340E-02, h = 2.338594347281745E-06 +step = 203, newt = 1, ldw = 5.328804149214178E+00, h = 2.054772408748004E-06 +step = 203, newt = 2, ldw = 7.220926525383435E-02, h = 2.054772408748004E-06 +step = 204, newt = 1, ldw = 5.330644494238911E+00, h = 1.806978365401942E-06 +step = 204, newt = 2, ldw = 7.179033258225749E-02, h = 1.806978365401942E-06 +step = 205, newt = 1, ldw = 5.332185302723991E+00, h = 1.590450192250794E-06 +step = 205, newt = 2, ldw = 7.141519384133184E-02, h = 1.590450192250794E-06 +step = 206, newt = 1, ldw = 5.333472592832081E+00, h = 1.401130413018420E-06 +step = 206, newt = 2, ldw = 7.106407346037459E-02, h = 1.401130413018420E-06 +step = 207, newt = 1, ldw = 5.334543222291096E+00, h = 1.235545035805828E-06 +step = 207, newt = 2, ldw = 7.071898915293817E-02, h = 1.235545035805828E-06 +step = 208, newt = 1, ldw = 5.335421568764881E+00, h = 1.090708999991115E-06 +step = 208, newt = 2, ldw = 7.036242218383949E-02, h = 1.090708999991115E-06 +step = 209, newt = 1, ldw = 5.336114869230205E+00, h = 9.640518323024979E-07 +step = 209, newt = 2, ldw = 6.997582609570417E-02, h = 9.640518323024979E-07 +step = 210, newt = 1, ldw = 5.336605210253879E+00, h = 8.533591902889159E-07 +step = 210, newt = 2, ldw = 6.953771424501103E-02, h = 8.533591902889159E-07 +step = 211, newt = 1, ldw = 5.336834455754417E+00, h = 7.567275861612305E-07 +step = 211, newt = 2, ldw = 6.902087987698256E-02, h = 7.567275861612305E-07 +step = 212, newt = 1, ldw = 5.336674383335784E+00, h = 6.725311446286318E-07 +step = 212, newt = 2, ldw = 6.838789666570431E-02, h = 6.725311446286318E-07 +step = 213, newt = 1, ldw = 5.335864420696983E+00, h = 5.994012395906195E-07 +step = 213, newt = 2, ldw = 6.758311936906049E-02, h = 5.994012395906195E-07 +step = 214, newt = 1, ldw = 5.333873164798693E+00, h = 5.362233095098820E-07 +step = 214, newt = 2, ldw = 6.651710683144024E-02, h = 5.362233095098820E-07 +step = 215, newt = 1, ldw = 5.329562898119689E+00, h = 4.821627900129052E-07 +step = 215, newt = 2, ldw = 6.503308002554024E-02, h = 4.821627900129052E-07 +step = 216, newt = 1, ldw = 5.320278279889004E+00, h = 4.367523471884444E-07 +step = 216, newt = 2, ldw = 6.282518269779171E-02, h = 4.367523471884444E-07 +step = 217, newt = 1, ldw = 5.298947582310787E+00, h = 4.001363771738594E-07 +step = 217, newt = 2, ldw = 5.920343189455466E-02, h = 4.001363771738594E-07 +step = 218, newt = 1, ldw = 5.242468918684148E+00, h = 3.738179974386896E-07 +step = 218, newt = 2, ldw = 5.223411553719494E-02, h = 3.738179974386896E-07 +step = 219, newt = 1, ldw = 4.894736404067086E+00, h = 3.601087749190793E-07 +step = 219, newt = 2, ldw = 3.259220396954212E-02, h = 3.601087749190793E-07 +step = 220, newt = 1, ldw = 2.995278405409576E+00, h = 3.674694136880568E-07 +step = 220, newt = 2, ldw = 4.072160016990256E-02, h = 3.674694136880568E-07 +step = 221, newt = 1, ldw = 2.432492728336804E+01, h = 4.649135609644224E-07 +step = 221, newt = 2, ldw = 1.593482292235732E-01, h = 4.649135609644224E-07 +step = 222, newt = 1, ldw = 1.753956372430814E+00, h = 2.304654265023678E-07 +step = 222, newt = 2, ldw = 8.488811866625182E-03, h = 2.304654265023678E-07 +step = 223, newt = 1, ldw = 1.505139245304236E+00, h = 1.705078791062005E-07 +step = 223, newt = 2, ldw = 3.727685690915748E-03, h = 1.705078791062005E-07 +step = 224, newt = 1, ldw = 3.905926185081940E+00, h = 2.087996869281053E-07 +step = 224, newt = 2, ldw = 4.759055609640884E-02, h = 2.087996869281053E-07 +step = 225, newt = 1, ldw = 5.147945289834421E+00, h = 2.002248216852341E-07 +step = 225, newt = 2, ldw = 1.189666824956438E-01, h = 2.002248216852341E-07 +step = 226, newt = 1, ldw = 3.793651471621994E+00, h = 2.126981000244191E-07 +step = 226, newt = 2, ldw = 1.953845309250715E-01, h = 2.126981000244191E-07 +step = 227, newt = 1, ldw = 1.582019905117913E+01, h = 2.701503457087577E-07 +step = 227, newt = 2, ldw = 6.672556681577569E-01, h = 2.701503457087577E-07 +step = 227, newt = 3, ldw = 2.829735294084822E-02, h = 2.701503457087577E-07 +step = 228, newt = 1, ldw = 2.771457719836512E+00, h = 1.656485554766988E-07 +step = 228, newt = 2, ldw = 3.032906159750670E-02, h = 1.656485554766988E-07 +step = 229, newt = 1, ldw = 2.690005297395255E+00, h = 1.302689427109105E-07 +step = 229, newt = 2, ldw = 3.580091937581353E-02, h = 1.302689427109105E-07 +step = 230, newt = 1, ldw = 5.238311913826688E+00, h = 1.709765176543430E-07 +step = 230, newt = 2, ldw = 1.202633588669660E-01, h = 1.709765176543430E-07 +step = 231, newt = 1, ldw = 3.162958941147973E+00, h = 1.964074580783571E-07 +step = 231, newt = 2, ldw = 2.463525326881805E-02, h = 1.964074580783571E-07 +step = 232, newt = 1, ldw = 1.456987721304101E+01, h = 2.145308166984650E-07 +step = 232, newt = 2, ldw = 5.254776756422619E-01, h = 2.145308166984650E-07 +step = 232, newt = 3, ldw = 1.079347729196802E-02, h = 2.145308166984650E-07 +step = 233, newt = 1, ldw = 5.949225590337140E+00, h = 1.622451189958177E-07 +step = 233, newt = 2, ldw = 1.321518406301049E-01, h = 1.622451189958177E-07 +step = 234, newt = 1, ldw = 5.549630679980282E+00, h = 1.438403891809873E-07 +step = 234, newt = 2, ldw = 5.921654169952881E-02, h = 1.438403891809873E-07 +step = 235, newt = 1, ldw = 7.032224591721828E+00, h = 1.692930978755450E-07 +step = 235, newt = 2, ldw = 5.842724293877350E-02, h = 1.692930978755450E-07 +step = 236, newt = 1, ldw = 6.095912749907521E+00, h = 1.918152562163818E-07 +step = 236, newt = 2, ldw = 1.014530350672047E-02, h = 1.918152562163818E-07 +step = 237, newt = 1, ldw = 1.492300707335655E+01, h = 3.174043527676529E-07 +step = 237, newt = 2, ldw = 6.086909359387785E-01, h = 3.174043527676529E-07 +step = 237, newt = 3, ldw = 4.092160490744735E-03, h = 3.174043527676529E-07 +step = 238, newt = 1, ldw = 2.735282155661100E+00, h = 2.041285167934734E-07 +step = 238, newt = 2, ldw = 6.988517284430940E-02, h = 2.041285167934734E-07 +step = 239, newt = 1, ldw = 5.783968405526431E+00, h = 1.548630754668562E-07 +step = 239, newt = 2, ldw = 2.247399143730663E-02, h = 1.548630754668562E-07 +step = 240, newt = 1, ldw = 7.149034495423872E+00, h = 1.619128274972494E-07 +step = 240, newt = 2, ldw = 1.688030472100792E-02, h = 1.619128274972494E-07 +step = 241, newt = 1, ldw = 1.071362074649449E+01, h = 1.712368662104244E-07 +step = 241, newt = 2, ldw = 1.638127126279445E-02, h = 1.712368662104244E-07 +step = 242, newt = 1, ldw = 1.172065609660135E+01, h = 1.666332559256971E-07 +step = 242, newt = 2, ldw = 9.981834979931705E-03, h = 1.666332559256971E-07 +step = 243, newt = 1, ldw = 1.136143199755095E+01, h = 1.630437719387266E-07 +step = 243, newt = 2, ldw = 1.506462419507239E-02, h = 1.630437719387266E-07 +step = 244, newt = 1, ldw = 1.104958929149188E+01, h = 1.609021565964247E-07 +step = 244, newt = 2, ldw = 3.240538973826578E-03, h = 1.609021565964247E-07 +step = 245, newt = 1, ldw = 1.088589596097441E+01, h = 1.597042602717144E-07 +step = 245, newt = 2, ldw = 5.142332829857319E-03, h = 1.597042602717144E-07 +step = 246, newt = 1, ldw = 1.079050776368519E+01, h = 1.589635992894994E-07 +step = 246, newt = 2, ldw = 6.308172564818647E-03, h = 1.589635992894994E-07 +step = 247, newt = 1, ldw = 1.073155608069720E+01, h = 1.585130973301072E-07 +step = 247, newt = 2, ldw = 7.017611653621868E-03, h = 1.585130973301072E-07 +step = 248, newt = 1, ldw = 1.069553602396324E+01, h = 1.582387032241664E-07 +step = 248, newt = 2, ldw = 7.452715458226738E-03, h = 1.582387032241664E-07 +step = 249, newt = 1, ldw = 1.067308162666863E+01, h = 1.580724192930508E-07 +step = 249, newt = 2, ldw = 7.720898292151053E-03, h = 1.580724192930508E-07 +step = 250, newt = 1, ldw = 1.065853261889702E+01, h = 1.579741511273571E-07 +step = 250, newt = 2, ldw = 7.886542168281838E-03, h = 1.579741511273571E-07 +step = 251, newt = 1, ldw = 1.064835721466377E+01, h = 1.579206391546316E-07 +step = 251, newt = 2, ldw = 7.988710470988257E-03, h = 1.579206391546316E-07 +step = 252, newt = 1, ldw = 1.064015356999463E+01, h = 1.578993444023682E-07 +step = 252, newt = 2, ldw = 8.051236481450444E-03, h = 1.578993444023682E-07 +step = 253, newt = 1, ldw = 1.063082921524216E+01, h = 1.578993444023682E-07 +step = 253, newt = 2, ldw = 8.087437864567648E-03, h = 1.578993444023682E-07 +step = 254, newt = 1, ldw = 1.061355414373560E+01, h = 1.578993444023682E-07 +step = 254, newt = 2, ldw = 8.101120986889162E-03, h = 1.578993444023682E-07 +step = 255, newt = 1, ldw = 1.058291866838955E+01, h = 1.578993444023682E-07 +step = 255, newt = 2, ldw = 8.094328481314280E-03, h = 1.578993444023682E-07 +step = 256, newt = 1, ldw = 1.053223334991578E+01, h = 1.578993444023682E-07 +step = 256, newt = 2, ldw = 8.065779649881312E-03, h = 1.578993444023682E-07 +step = 257, newt = 1, ldw = 1.045070677357706E+01, h = 1.578993444023682E-07 +step = 257, newt = 2, ldw = 8.009595276848674E-03, h = 1.578993444023682E-07 +step = 258, newt = 1, ldw = 1.032169669915536E+01, h = 1.578993444023682E-07 +step = 258, newt = 2, ldw = 7.914501554593329E-03, h = 1.578993444023682E-07 +step = 259, newt = 1, ldw = 1.012063265421836E+01, h = 1.578993444023682E-07 +step = 259, newt = 2, ldw = 7.762577178878479E-03, h = 1.578993444023682E-07 +step = 260, newt = 1, ldw = 9.813380774811158E+00, h = 1.578993444023682E-07 +step = 260, newt = 2, ldw = 7.528211811830420E-03, h = 1.578993444023682E-07 +step = 261, newt = 1, ldw = 9.357025851460405E+00, h = 1.578993444023682E-07 +step = 261, newt = 2, ldw = 7.178839552500416E-03, h = 1.578993444023682E-07 +step = 262, newt = 1, ldw = 8.706697269318003E+00, h = 1.578993444023682E-07 +step = 262, newt = 2, ldw = 6.680260279304942E-03, h = 1.578993444023682E-07 +step = 263, newt = 1, ldw = 7.832410433278051E+00, h = 1.578993444023682E-07 +step = 263, newt = 2, ldw = 6.009614634918760E-03, h = 1.578993444023682E-07 +step = 264, newt = 1, ldw = 5.583379407978192E+00, h = 1.578993444023682E-07 +step = 264, newt = 2, ldw = 4.284024357680455E-03, h = 1.578993444023682E-07 +step = 265, newt = 1, ldw = 2.903452478922570E+00, h = 1.578993444023682E-07 +step = 266, newt = 1, ldw = 5.361325899637640E+00, h = 2.328670980694690E-07 +step = 266, newt = 2, ldw = 5.800760316847878E-03, h = 2.328670980694690E-07 +step = 267, newt = 1, ldw = 5.429411663014074E+00, h = 2.636257662068029E-07 +step = 267, newt = 2, ldw = 1.664896456523471E-07, h = 2.636257662068029E-07 +step = 268, newt = 1, ldw = 5.158004272705093E+00, h = 3.257419558540237E-07 +step = 269, newt = 1, ldw = 6.147391581634168E+00, h = 4.430046610387730E-07 +step = 270, newt = 1, ldw = 6.082321881647355E+00, h = 6.063675547743876E-07 +step = 271, newt = 1, ldw = 5.342432773604600E+00, h = 9.222214642539324E-07 +step = 272, newt = 1, ldw = 4.133449512366531E+00, h = 1.686535928577660E-06 +step = 273, newt = 1, ldw = 2.366057048610498E+00, h = 4.471378605518322E-06 +step = 273, newt = 2, ldw = 1.380863677123397E-05, h = 4.471378605518322E-06 +step = 274, newt = 1, ldw = 7.630946815902783E-01, h = 2.064344418825020E-05 +step = 275, newt = 1, ldw = 3.891184207254365E-01, h = 1.651475535060016E-04 +step = 276, newt = 1, ldw = 1.848326068641797E-02, h = 1.321180428048013E-03 +step = 277, newt = 1, ldw = 4.277275522585506E-01, h = 1.056944342438410E-02 +step = 278, newt = 1, ldw = 4.756393570515869E+00, h = 8.455554739507280E-02 +step = 278, newt = 2, ldw = 3.849872589101992E-01, h = 8.455554739507280E-02 +step = 278, newt = 3, ldw = 3.585805601814212E-02, h = 8.455554739507280E-02 +step = 279, newt = 1, ldw = 1.386212650803126E+00, h = 2.132444924152443E-01 +step = 279, newt = 2, ldw = 3.537056439008263E-01, h = 2.132444924152443E-01 +step = 279, newt = 3, ldw = 8.537535248461732E-02, h = 2.132444924152443E-01 +step = 279, newt = 4, ldw = 2.024952321809702E-02, h = 2.132444924152443E-01 +step = 280, newt = 1, ldw = 1.375185240629274E+00, h = 7.582774924658886E-02 +step = 280, newt = 2, ldw = 1.133910796759050E-01, h = 7.582774924658886E-02 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 2248 +Number of Jacobian evaluations = 162 +Number of factorizations = 252 +Number of lin sys solutions = 668 +Number of performed steps = 280 +Number of accepted steps = 242 +Number of rejected steps = 8 +Number of iterations (maximum) = 6 +y = 1.706163410178079E+00 -8.927971289301175E-01 +h = 1.510987221365367E-01 diff --git a/russell_ode/data/russell_dopri5_arenstorf.txt b/russell_ode/data/russell_dopri5_arenstorf.txt new file mode 100644 index 00000000..dd3e2854 --- /dev/null +++ b/russell_ode/data/russell_dopri5_arenstorf.txt @@ -0,0 +1,27 @@ + +running 1 test +step = 0, x = 0.00, y = 9.940000000000000E-01 0.000000000000000E+00 0.000000000000000E+00 -2.001585106379082E+00 +step = 47, x = 1.00, y = 3.132838429323184E-01 3.480091155210447E-01 -1.042618215839896E+00 6.733839248384373E-01 +step = 62, x = 2.00, y = -5.798780533212067E-01 6.090775695399071E-01 -4.225300697828142E-01 2.442220183560238E-01 +step = 69, x = 3.00, y = -6.223461712176216E-01 9.682675050144309E-01 2.788054332233766E-01 3.604394979563333E-01 +step = 75, x = 4.00, y = -1.983335162291612E-01 1.137638045474967E+00 4.486524729640530E-01 -6.688570025290327E-02 +step = 82, x = 5.00, y = 2.268867961333854E-02 8.665401519108186E-01 -1.177361292734163E-01 -4.217863140457993E-01 +step = 93, x = 6.00, y = -4.735744349582001E-01 2.239068009407728E-01 -5.609488561287610E-01 -9.861372030076758E-01 +step = 105, x = 7.00, y = -8.157792084387205E-01 -4.352867069546228E-01 -3.627947927416372E-01 -2.046655236855462E-01 +step = 112, x = 8.00, y = -1.174553319989369E+00 -2.759466957078963E-01 -2.531713451618869E-01 4.473768609451443E-01 +step = 118, x = 9.00, y = -1.190202656712986E+00 2.459675968229495E-01 2.264445030412676E-01 4.714257090529617E-01 +step = 124, x = 10.00, y = -8.398073586249322E-01 4.468300944738623E-01 3.737429154754564E-01 -1.496700318779169E-01 +step = 134, x = 11.00, y = -5.081113096370156E-01 -1.592073708757419E-01 4.978491909808826E-01 -9.944444141765316E-01 +step = 147, x = 12.00, y = 1.314719824475574E-02 -8.385751955512947E-01 1.752757009858996E-01 -4.358668845305712E-01 +step = 155, x = 13.00, y = -1.695504654596519E-01 -1.132227264074381E+00 -4.333572977282618E-01 -9.895216108659885E-02 +step = 160, x = 14.00, y = -6.031127884195211E-01 -9.912598152028734E-01 -3.104960656204045E-01 3.441899768169649E-01 +step = 167, x = 15.00, y = -6.055709668042412E-01 -6.258686162320628E-01 3.659167703116567E-01 2.704440021697843E-01 +step = 179, x = 16.00, y = 2.427114697986926E-01 -3.899946986984286E-01 1.118813953274377E+00 6.095882055300471E-01 +step = 197, x = 17.00, y = 9.413011551550116E-01 3.531669084657405E-02 6.983649875996141E-01 -1.853067154792517E-01 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 1429 +Number of performed steps = 238 +Number of accepted steps = 217 +Number of rejected steps = 21 +y = 9.940021704035319E-01 9.040892630699887E-06 1.459758564420648E-03 -2.001245515761783E+00 +h = 5.258587321247462E-04 diff --git a/russell_ode/data/russell_dopri5_arenstorf_debug.txt b/russell_ode/data/russell_dopri5_arenstorf_debug.txt new file mode 100644 index 00000000..7e5723f9 --- /dev/null +++ b/russell_ode/data/russell_dopri5_arenstorf_debug.txt @@ -0,0 +1,247 @@ + +running 1 test +step(A) = 1, err = 1.296462452906261E-04, h_new = 2.851499626442085E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 2, err = 2.154811640415047E-02, h_new = 3.444662229048353E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 3, err = 4.641537220386778E-02, h_new = 4.481186434553215E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 4, err = 1.300205391996086E-01, h_new = 5.045682997850971E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 5, err = 1.638798127834997E-01, h_new = 5.691852298444084E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 6, err = 1.953077586530272E-01, h_new = 6.290060682165379E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 7, err = 2.037616908291114E-01, h_new = 6.949847795050713E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 8, err = 2.098121565486737E-01, h_new = 7.653701038122722E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 9, err = 2.094489819992008E-01, h_new = 8.441194871218945E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 10, err = 2.043646634566212E-01, h_new = 9.348040369788355E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 11, err = 1.958605789575845E-01, h_new = 1.041713578396430E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 12, err = 1.867805997908780E-01, h_new = 1.168267555094211E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 13, err = 1.784989553453886E-01, h_new = 1.317831943501003E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 14, err = 1.713743465741034E-01, h_new = 1.494160251779021E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 15, err = 1.651983983221194E-01, h_new = 1.701909762064384E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 16, err = 1.598307260655731E-01, h_new = 1.946601375039082E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 17, err = 1.548855185528257E-01, h_new = 2.235445610598754E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 18, err = 1.502839933371166E-01, h_new = 2.577103651158404E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 19, err = 1.459632466384033E-01, h_new = 2.982150157000738E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 20, err = 1.419097553626065E-01, h_new = 3.463376250520752E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 21, err = 1.381058585951810E-01, h_new = 4.036329542982712E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 22, err = 1.345336635217833E-01, h_new = 4.719938560868405E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 23, err = 1.311708629380790E-01, h_new = 5.537321502409488E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 24, err = 1.279946362195580E-01, h_new = 6.516781251837187E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 25, err = 1.249836639326536E-01, h_new = 7.693044962994749E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 26, err = 1.221206556377653E-01, h_new = 9.108791445896684E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 27, err = 1.193944032909891E-01, h_new = 1.081652001791648E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 28, err = 1.168020491098307E-01, h_new = 1.288080032791421E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 29, err = 1.143515175432437E-01, h_new = 1.538091766826002E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 30, err = 1.120637767053275E-01, h_new = 1.841388136889996E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 31, err = 1.099734916969470E-01, h_new = 2.209772161594295E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 32, err = 1.081253439979009E-01, h_new = 2.657503546257194E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 33, err = 1.060207982503169E-01, h_new = 3.204475683615544E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 34, err = 1.047664155949133E-01, h_new = 3.868809789339650E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 35, err = 1.050191701917828E-01, h_new = 4.666734696085169E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 36, err = 1.061519578926093E-01, h_new = 5.619512002934710E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 37, err = 1.129641666409209E-01, h_new = 6.698511595491312E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 38, err = 1.490676597345560E-01, h_new = 7.635953365107344E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 39, err = 2.552125615805197E-01, h_new = 8.032815021869603E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 40, err = 3.915024970271491E-01, h_new = 8.028261061449600E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 41, err = 5.175985653232011E-01, h_new = 7.783851759800908E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 42, err = 6.092736993434873E-01, h_new = 7.422986574410036E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 43, err = 6.609447381082678E-01, h_new = 7.027253271228893E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 44, err = 6.848737616950651E-01, h_new = 6.634083477081394E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 45, err = 7.001897686969242E-01, h_new = 6.248290073972212E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 46, err = 7.051668335907338E-01, h_new = 5.883052227397750E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 47, err = 6.890558296789413E-01, h_new = 5.562546295508042E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 48, err = 6.296642668880398E-01, h_new = 5.335778420016586E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 49, err = 5.462361447642531E-01, h_new = 5.224563160314249E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 50, err = 4.615041011325203E-01, h_new = 5.234532066153283E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 51, err = 3.901163166639435E-01, h_new = 5.360241996601335E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 52, err = 3.312625348622850E-01, h_new = 5.605900389248250E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 53, err = 2.947697207836194E-01, h_new = 5.941316535719518E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 54, err = 2.847043230886980E-01, h_new = 6.304599811454697E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 55, err = 2.858210237764336E-01, h_new = 6.676360631781238E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 56, err = 2.858704440472649E-01, h_new = 7.070942250088545E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 57, err = 2.791391822737960E-01, h_new = 7.519293442976975E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 58, err = 2.683971713546929E-01, h_new = 8.041926996142641E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 59, err = 2.581463136907683E-01, h_new = 8.644433653254566E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 60, err = 2.499502437268744E-01, h_new = 9.328645420749301E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 61, err = 2.455448501750316E-01, h_new = 1.008446810850529E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 62, err = 2.458366591211932E-01, h_new = 1.089157809537041E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 63, err = 2.514770049527550E-01, h_new = 1.171856619528765E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 64, err = 2.624735275239095E-01, h_new = 1.252830685067876E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 65, err = 2.779076303639804E-01, h_new = 1.328725382934478E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 66, err = 2.956698023269771E-01, h_new = 1.397644033072334E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 67, err = 2.887912325340205E-01, h_new = 1.479694577469836E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 68, err = 2.893389018342534E-01, h_new = 1.564583662494045E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 69, err = 2.976619075251505E-01, h_new = 1.646510934812067E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 70, err = 3.106968292850212E-01, h_new = 1.722101716210216E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 71, err = 3.367687827142479E-01, h_new = 1.779706436948914E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 72, err = 3.864260112082192E-01, h_new = 1.802531545491529E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 73, err = 4.537563537098524E-01, h_new = 1.786274403963194E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 74, err = 5.117370183265136E-01, h_new = 1.745523224331829E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 75, err = 5.435151887916555E-01, h_new = 1.696461577203107E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 76, err = 5.669452578891402E-01, h_new = 1.640941176862364E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 77, err = 5.865556485215274E-01, h_new = 1.580755130296964E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 78, err = 6.130968779355183E-01, h_new = 1.513420214589579E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 79, err = 6.506815598448598E-01, h_new = 1.436913282334407E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 80, err = 7.015364003593220E-01, h_new = 1.350141457603675E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 81, err = 7.192280263489695E-01, h_new = 1.267057921521672E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 82, err = 7.008369415463981E-01, h_new = 1.195525253704024E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 83, err = 7.283986628778332E-01, h_new = 1.119497658247369E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 84, err = 7.435047701075275E-01, h_new = 1.046266272105551E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 85, err = 7.568229195667454E-01, h_new = 9.756792264681263E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 86, err = 7.616522249372375E-01, h_new = 9.095167409380432E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 87, err = 7.707728232685892E-01, h_new = 8.463421851683976E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 88, err = 7.981918331386748E-01, h_new = 7.832624667450215E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 89, err = 8.230117240190905E-01, h_new = 7.221295100546921E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 90, err = 7.851702478854412E-01, h_new = 6.719392207168697E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 91, err = 6.997284358291719E-01, h_new = 6.364041971843419E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 92, err = 5.918024754603728E-01, h_new = 6.173090919064988E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 93, err = 4.899521802317282E-01, h_new = 6.141947867965784E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 94, err = 4.052105737776681E-01, h_new = 6.263961103672716E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 95, err = 3.409407693172497E-01, h_new = 6.528950459315042E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 96, err = 2.982438829674549E-01, h_new = 6.913785119188497E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 97, err = 2.689044899688578E-01, h_new = 7.411559160998341E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 98, err = 2.666962242041169E-01, h_new = 7.923428509630714E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 99, err = 2.744209036105965E-01, h_new = 8.426852758513871E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 100, err = 2.775707991372357E-01, h_new = 8.955112802239185E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 101, err = 2.751111668877671E-01, h_new = 9.535250876748341E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 102, err = 2.716483602025098E-01, h_new = 1.017123661043489E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 103, err = 2.706064175468209E-01, h_new = 1.085123264470905E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 104, err = 2.743806147136225E-01, h_new = 1.154768757738387E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 105, err = 2.838995862255634E-01, h_new = 1.222457200156739E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 106, err = 2.989933131980786E-01, h_new = 1.284518347115366E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 107, err = 3.182974437582698E-01, h_new = 1.338220395635753E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 108, err = 2.884695019008711E-01, h_new = 1.421236970746043E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 109, err = 2.979575089856427E-01, h_new = 1.495225740988897E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 110, err = 3.044004495335145E-01, h_new = 1.569385908027952E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 111, err = 3.175699230956188E-01, h_new = 1.636806638602781E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 112, err = 3.334744163441235E-01, h_new = 1.695871076329604E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 113, err = 3.592470521197352E-01, h_new = 1.738365111584286E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 114, err = 3.994700316033699E-01, h_new = 1.755282086015111E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 115, err = 4.503645643472563E-01, h_new = 1.743985549592200E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 116, err = 4.353794042646296E-01, h_new = 1.751138193134665E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 117, err = 4.495025709418646E-01, h_new = 1.746438007756733E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 118, err = 4.800675531755672E-01, h_new = 1.724580895770875E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 119, err = 5.192798992211116E-01, h_new = 1.684844860323805E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 120, err = 5.610233993726678E-01, h_new = 1.629640170813895E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 121, err = 6.069521753494785E-01, h_new = 1.560117145402272E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 122, err = 6.614819761068911E-01, h_new = 1.476514905168932E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 123, err = 7.020583619245798E-01, h_new = 1.388089978253623E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 124, err = 6.395304926810540E-01, h_new = 1.328980605846282E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 125, err = 6.702741951866534E-01, h_new = 1.257571475176939E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 126, err = 6.902293017270548E-01, h_new = 1.186305136195486E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 127, err = 7.182825870946070E-01, h_new = 1.112829034907528E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 128, err = 7.378581884782361E-01, h_new = 1.040800226565141E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 129, err = 7.547839857035502E-01, h_new = 9.707311107787331E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 130, err = 7.763173651158625E-01, h_new = 9.018777731895330E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 131, err = 8.254682963458150E-01, h_new = 8.301425938133886E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 132, err = 8.791147131806452E-01, h_new = 7.578364323028892E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 133, err = 8.418055151448146E-01, h_new = 6.987049351288303E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 134, err = 7.431141964209597E-01, h_new = 6.568487019770342E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 135, err = 6.181896471355116E-01, h_new = 6.339563905764106E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 136, err = 5.075968869309007E-01, h_new = 6.280707107835129E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 137, err = 4.172546351738891E-01, h_new = 6.382687912466029E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 138, err = 3.483078175711632E-01, h_new = 6.636336791610575E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 139, err = 3.010852641762883E-01, h_new = 7.022184589408612E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 140, err = 2.808674005223051E-01, h_new = 7.475101670231450E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 141, err = 2.868667044082056E-01, h_new = 7.906677814190691E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 142, err = 2.980642197883314E-01, h_new = 8.315934954889695E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 143, err = 2.990687466575725E-01, h_new = 8.754773441743352E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 144, err = 2.928980282167511E-01, h_new = 9.250739816279263E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 145, err = 2.853741396586608E-01, h_new = 9.809958023689061E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 146, err = 2.805481504526680E-01, h_new = 1.042233392134352E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 147, err = 2.805394630932941E-01, h_new = 1.106544326526941E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 148, err = 2.866652139032872E-01, h_new = 1.170515945895408E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 149, err = 2.870276102450379E-01, h_new = 1.238990050728571E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 150, err = 2.681531426382271E-01, h_new = 1.326789982637305E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 151, err = 2.797067320282939E-01, h_new = 1.406826410339066E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 152, err = 2.913677380403096E-01, h_new = 1.483870805349092E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 153, err = 3.079613565941168E-01, h_new = 1.553001589267741E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 154, err = 3.223789717031385E-01, h_new = 1.616337014908377E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 155, err = 3.348488401610762E-01, h_new = 1.674498644982241E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 156, err = 3.466602505567860E-01, h_new = 1.727179894646785E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 157, err = 3.580837726135986E-01, h_new = 1.774184808850710E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 158, err = 3.849741231996160E-01, h_new = 1.802508784238243E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 159, err = 4.355693244930899E-01, h_new = 1.798446082364043E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 160, err = 5.023688182707522E-01, h_new = 1.760063628010115E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 161, err = 5.674041136045804E-01, h_new = 1.696875827678934E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 162, err = 6.275729338994865E-01, h_new = 1.616014885692230E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 163, err = 6.915375964510854E-01, h_new = 1.519937566912938E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 164, err = 6.892316714760274E-01, h_new = 1.435948294267497E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 165, err = 6.867369979963295E-01, h_new = 1.357255295519515E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 166, err = 7.170377798795941E-01, h_new = 1.273308169403260E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 167, err = 7.540841775642508E-01, h_new = 1.186414226182513E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 168, err = 7.982648006439236E-01, h_new = 1.097010189957071E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 169, err = 8.429341078682930E-01, h_new = 1.007289098303264E-01, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 170, err = 8.793984925923689E-01, h_new = 9.202732880942509E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 171, err = 8.904399690790382E-01, h_new = 8.404153278727183E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 172, err = 8.675898074168362E-01, h_new = 7.712713602533357E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 173, err = 8.376857685054876E-01, h_new = 7.113093192916750E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 174, err = 8.306879774239435E-01, h_new = 6.560241185309085E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 175, err = 8.296491053220226E-01, h_new = 6.049615551614661E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 176, err = 7.401106717329440E-01, h_new = 5.687817204689823E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 177, err = 6.321875667447755E-01, h_new = 5.467845002878122E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 178, err = 5.239426571395656E-01, h_new = 5.392799870007225E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 179, err = 4.344886446775811E-01, h_new = 5.449690396908460E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 180, err = 3.661787371040170E-01, h_new = 5.627372268407711E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 181, err = 3.213054344655349E-01, h_new = 5.900921106563926E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 182, err = 3.029053099750587E-01, h_new = 6.217515403123592E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 183, err = 3.057263634901061E-01, h_new = 6.525368818900590E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 184, err = 3.140701346536158E-01, h_new = 6.819717102562205E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 185, err = 3.152411732314628E-01, h_new = 7.130510694724877E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 186, err = 3.125975594361295E-01, h_new = 7.467260690983023E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 187, err = 3.152321864831897E-01, h_new = 7.806134894324368E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 188, err = 3.488908522356757E-01, h_new = 8.023549835530422E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 189, err = 5.062805555195562E-01, h_new = 7.772668309179404E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 190, err = 9.592416030125985E-01, h_new = 6.855837786132282E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 191, err = 1.807186684617305E+00, h_new = 5.579721591401974E-02 +step(A) = 192, err = 5.340525999430816E-01, h_new = 5.577542312952402E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 193, err = 2.167530747295673E+00, h_new = 4.401201859110335E-02 +step(A) = 194, err = 5.287815995942098E-01, h_new = 4.304871394568201E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 195, err = 2.025260844762286E+00, h_new = 3.436377975245031E-02 +step(A) = 196, err = 5.289129231685409E-01, h_new = 3.359689788174636E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 197, err = 1.989732456588587E+00, h_new = 2.689964771753819E-02 +step(A) = 198, err = 5.332864260021275E-01, h_new = 2.626280922898557E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 199, err = 1.928482645431665E+00, h_new = 2.113961207631778E-02 +step(A) = 200, err = 5.362725600550525E-01, h_new = 2.062635063775585E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 201, err = 1.865738412433598E+00, h_new = 1.669630146626335E-02 +step(A) = 202, err = 5.393832638458662E-01, h_new = 1.627854711279448E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 203, err = 1.797474056066051E+00, h_new = 1.326067108399834E-02 +step(A) = 204, err = 5.424828789845398E-01, h_new = 1.291927923107471E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 205, err = 1.730073575132070E+00, h_new = 1.059277669384622E-02 +step(A) = 206, err = 5.454617696757345E-01, h_new = 1.031282941193582E-02, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 207, err = 1.664923783782274E+00, h_new = 8.511053151396938E-03 +step(A) = 208, err = 5.482938027249568E-01, h_new = 8.280644067978047E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 209, err = 1.602073735528661E+00, h_new = 6.878767042819656E-03 +step(A) = 210, err = 5.509805558062723E-01, h_new = 6.688372897640138E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 211, err = 1.541158523362831E+00, h_new = 5.592795564498950E-03 +step(A) = 212, err = 5.535289764017193E-01, h_new = 5.434793455570172E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 213, err = 1.481755819213476E+00, h_new = 4.575025553696103E-03 +step(A) = 214, err = 5.559385805092707E-01, h_new = 4.443314865732131E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 215, err = 1.423596436230461E+00, h_new = 3.765943737324327E-03 +step(A) = 216, err = 5.581900276287672E-01, h_new = 3.655648722402518E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 217, err = 1.366767801833750E+00, h_new = 3.119886973129718E-03 +step(A) = 218, err = 5.602305992664471E-01, h_new = 3.027124567334649E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 219, err = 1.311974795922494E+00, h_new = 2.601509813599447E-03 +step(A) = 220, err = 5.619561729196878E-01, h_new = 2.523209058509085E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 221, err = 1.260773835390737E+00, h_new = 2.183169335773458E-03 +step(A) = 222, err = 5.632011165723834E-01, h_new = 2.116923839926267E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 223, err = 1.215277943589513E+00, h_new = 1.843116930733157E-03 +step(A) = 224, err = 5.637755042909462E-01, h_new = 1.787038396489424E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 225, err = 1.175475066682524E+00, h_new = 1.564732530880336E-03 +step(A) = 226, err = 5.637942230179203E-01, h_new = 1.517177395597043E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 227, err = 1.130987485708541E+00, h_new = 1.337183617061853E-03 +step(A) = 228, err = 5.636537657326289E-01, h_new = 1.296600758038143E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 229, err = 1.078062359365104E+00, h_new = 1.152124257258525E-03 +step(A) = 230, err = 5.637827544138823E-01, h_new = 1.117103268928840E-03, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(R) = 231, err = 1.008062060507038E+00, h_new = 1.004021461587276E-03 +step(A) = 232, err = 5.641344707137383E-01, h_new = 9.734080393864043E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 233, err = 9.272490976722300E-01, h_new = 8.673000120241186E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 234, err = 9.417029555314325E-01, h_new = 7.862023723096077E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 235, err = 9.860534014435050E-01, h_new = 7.075715176971904E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 236, err = 9.598395915745764E-01, h_new = 6.409071215786475E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 237, err = 8.850310699346592E-01, h_new = 5.879529926628184E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +step(A) = 238, err = 1.819999416400736E-01, h_new = 5.258587321247462E-04, n_yes = 0, n_no = 0, h*lambda = 0.000000000000000E+00 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 1429 +Number of performed steps = 238 +Number of accepted steps = 217 +Number of rejected steps = 21 +y = 9.940021704035319E-01 9.040892630699887E-06 1.459758564420648E-03 -2.001245515761783E+00 +h = 5.258587321247462E-04 diff --git a/russell_ode/data/russell_dopri5_hairer_wanner_eq1.txt b/russell_ode/data/russell_dopri5_hairer_wanner_eq1.txt new file mode 100644 index 00000000..05b85986 --- /dev/null +++ b/russell_ode/data/russell_dopri5_hairer_wanner_eq1.txt @@ -0,0 +1,24 @@ + +running 1 test +step = 0, x = 0.00, y = 0.000000000000000E+00 +step = 10, x = 0.10, y = 9.898623749945524E-01 +step = 14, x = 0.20, y = 9.835855727508487E-01 +step = 16, x = 0.30, y = 9.608730705304842E-01 +step = 18, x = 0.40, y = 9.284913897782356E-01 +step = 20, x = 0.50, y = 8.868301683931109E-01 +step = 22, x = 0.60, y = 8.363069895334084E-01 +step = 24, x = 0.70, y = 7.774254044394396E-01 +step = 26, x = 0.80, y = 7.107729344779837E-01 +step = 28, x = 0.90, y = 6.370146442170533E-01 +step = 30, x = 1.00, y = 5.568866965217578E-01 +step = 32, x = 1.10, y = 4.711902738440203E-01 +step = 33, x = 1.20, y = 3.807874366082720E-01 +step = 35, x = 1.30, y = 2.866070362994957E-01 +step = 37, x = 1.40, y = 1.895941168686506E-01 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 235 +Number of performed steps = 39 +Number of accepted steps = 39 +Number of rejected steps = 0 +y = 9.063921649308775E-02 +h = 5.809633059169564E-02 diff --git a/russell_ode/data/russell_dopri5_van_der_pol_debug.txt b/russell_ode/data/russell_dopri5_van_der_pol_debug.txt new file mode 100644 index 00000000..fac5b06b --- /dev/null +++ b/russell_ode/data/russell_dopri5_van_der_pol_debug.txt @@ -0,0 +1,438 @@ + +running 1 test +step(A) = 1, err = 3.727760587632517E-06, h_new = 5.213103499049211E-04, n_yes = 0, n_no = 1, h*lambda = 9.997470703052302E-02 +step(A) = 2, err = 1.240953492565221E-02, h_new = 6.845425066483809E-04, n_yes = 0, n_no = 2, h*lambda = 5.212932725671368E-01 +step(A) = 3, err = 2.688194416522675E-02, h_new = 9.558391963459037E-04, n_yes = 0, n_no = 3, h*lambda = 6.844363978417670E-01 +step(A) = 4, err = 7.358690218642597E-02, h_new = 1.159977816137417E-03, n_yes = 0, n_no = 4, h*lambda = 9.551832656622222E-01 +step(A) = 5, err = 7.734184510519691E-02, h_new = 1.453227956752059E-03, n_yes = 0, n_no = 5, h*lambda = 1.158113848817905E+00 +step(A) = 6, err = 8.155247362357607E-02, h_new = 1.807875507566744E-03, n_yes = 0, n_no = 6, h*lambda = 1.449058825396815E+00 +step(A) = 7, err = 6.439628755194876E-02, h_new = 2.346184573147208E-03, n_yes = 0, n_no = 7, h*lambda = 1.799776360951090E+00 +step(A) = 8, err = 5.074827815242944E-02, h_new = 3.140779839561930E-03, n_yes = 0, n_no = 8, h*lambda = 2.330763902751608E+00 +step(A) = 9, err = 5.307555787769205E-02, h_new = 4.132994048958958E-03, n_yes = 0, n_no = 9, h*lambda = 3.111350910816916E+00 +step(A) = 10, err = 1.828230653883824E-01, h_new = 4.415275469087175E-03, n_yes = 1, n_no = 0, h*lambda = 4.079076160821990E+00 +step(A) = 11, err = 9.983777781482974E-01, h_new = 3.713653838214340E-03, n_yes = 2, n_no = 0, h*lambda = 4.340439785236608E+00 +step(R) = 12, err = 1.958559439661465E+00, h_new = 2.981361783315531E-03 +step(A) = 13, err = 5.429798041882616E-01, h_new = 2.976567673106566E-03, n_yes = 2, n_no = 1, h*lambda = 2.922935427784793E+00 +step(A) = 14, err = 2.604231783662490E-01, h_new = 3.286124999537922E-03, n_yes = 2, n_no = 2, h*lambda = 2.910250138590204E+00 +step(A) = 15, err = 2.185708904387990E-01, h_new = 3.629301761797776E-03, n_yes = 2, n_no = 3, h*lambda = 3.203240293387299E+00 +step(A) = 16, err = 3.190115285485447E-01, h_new = 3.732520464830527E-03, n_yes = 3, n_no = 0, h*lambda = 3.525998778100285E+00 +step(A) = 17, err = 5.522585322563178E-01, h_new = 3.550035927658765E-03, n_yes = 4, n_no = 0, h*lambda = 3.613863991489044E+00 +step(A) = 18, err = 7.044683881446998E-01, h_new = 3.311498010542711E-03, n_yes = 5, n_no = 0, h*lambda = 3.425947123554724E+00 +step(A) = 19, err = 5.762683559832782E-01, h_new = 3.227566930138650E-03, n_yes = 5, n_no = 1, h*lambda = 3.185913475883028E+00 +step(A) = 20, err = 3.923779967123939E-01, h_new = 3.331294038257659E-03, n_yes = 5, n_no = 2, h*lambda = 3.095782642828231E+00 +step(A) = 21, err = 3.150183898278662E-01, h_new = 3.514686219593597E-03, n_yes = 5, n_no = 3, h*lambda = 3.185287472112490E+00 +step(A) = 22, err = 3.399300730430000E-01, h_new = 3.628492873524443E-03, n_yes = 6, n_no = 0, h*lambda = 3.349542976403311E+00 +step(A) = 23, err = 4.377868682190208E-01, h_new = 3.599230854916274E-03, n_yes = 7, n_no = 0, h*lambda = 3.446184457473290E+00 +step(A) = 24, err = 5.314409248846307E-01, h_new = 3.489597785948703E-03, n_yes = 8, n_no = 0, h*lambda = 3.406762217147994E+00 +step(A) = 25, err = 5.258363701602446E-01, h_new = 3.415792466048016E-03, n_yes = 9, n_no = 0, h*lambda = 3.292033490403430E+00 +step(A) = 26, err = 4.464080548573433E-01, h_new = 3.436477991048701E-03, n_yes = 9, n_no = 1, h*lambda = 3.211875898654048E+00 +step(A) = 27, err = 3.833585347434046E-01, h_new = 3.524783801346737E-03, n_yes = 9, n_no = 2, h*lambda = 3.220655297220887E+00 +step(A) = 28, err = 3.745240530597944E-01, h_new = 3.607676997474531E-03, n_yes = 10, n_no = 0, h*lambda = 3.292190171398080E+00 +step(A) = 29, err = 4.125127147932012E-01, h_new = 3.628983534445074E-03, n_yes = 11, n_no = 0, h*lambda = 3.357856986288300E+00 +step(A) = 30, err = 4.629698432215407E-01, h_new = 3.593362582514143E-03, n_yes = 12, n_no = 0, h*lambda = 3.365788505508787E+00 +step(A) = 31, err = 4.807404728724885E-01, h_new = 3.551737348006154E-03, n_yes = 13, n_no = 0, h*lambda = 3.321068963245854E+00 +step(A) = 32, err = 4.552668253453161E-01, h_new = 3.548579653715505E-03, n_yes = 14, n_no = 0, h*lambda = 3.271163656930352E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 8.973510130816317E-02, ACCEPTED STEP = 32 +step(A) = 33, err = 4.187997643678076E-01, h_new = 3.588282399522409E-03, n_yes = 15, n_no = 0, h*lambda = 3.256824769569686E+00 +step(A) = 34, err = 4.023721900723629E-01, h_new = 3.641016347314622E-03, n_yes = 16, n_no = 0, h*lambda = 3.281567253670353E+00 +step(A) = 35, err = 4.128776652616323E-01, h_new = 3.672489985558814E-03, n_yes = 17, n_no = 0, h*lambda = 3.317746879037413E+00 +step(A) = 36, err = 4.372081522524695E-01, h_new = 3.672137905119940E-03, n_yes = 18, n_no = 0, h*lambda = 3.334163600768229E+00 +step(A) = 37, err = 4.534015784166335E-01, h_new = 3.657521664994861E-03, n_yes = 19, n_no = 0, h*lambda = 3.321571853460233E+00 +step(A) = 38, err = 4.491089007346002E-01, h_new = 3.654171745491744E-03, n_yes = 20, n_no = 0, h*lambda = 3.296159844234698E+00 +step(A) = 39, err = 4.322176200202851E-01, h_new = 3.673297571645057E-03, n_yes = 21, n_no = 0, h*lambda = 3.280955619905970E+00 +step(A) = 40, err = 4.191700439020404E-01, h_new = 3.706127699015059E-03, n_yes = 22, n_no = 0, h*lambda = 3.285802075574862E+00 +step(A) = 41, err = 4.190025846061023E-01, h_new = 3.734923053990436E-03, n_yes = 23, n_no = 0, h*lambda = 3.302611873943558E+00 +step(A) = 42, err = 4.290236789614348E-01, h_new = 3.748789215378067E-03, n_yes = 24, n_no = 0, h*lambda = 3.315509301368793E+00 +step(A) = 43, err = 4.392810014285364E-01, h_new = 3.751168478178247E-03, n_yes = 25, n_no = 0, h*lambda = 3.314948466779651E+00 +step(A) = 44, err = 4.412782483731278E-01, h_new = 3.754202097902657E-03, n_yes = 26, n_no = 0, h*lambda = 3.304151076523398E+00 +step(A) = 45, err = 4.349692823542790E-01, h_new = 3.767130787642701E-03, n_yes = 27, n_no = 0, h*lambda = 3.293884821665563E+00 +step(A) = 46, err = 4.271791251748280E-01, h_new = 3.789551797221788E-03, n_yes = 28, n_no = 0, h*lambda = 3.292185526777097E+00 +step(A) = 47, err = 4.242905792583735E-01, h_new = 3.813747902134716E-03, n_yes = 29, n_no = 0, h*lambda = 3.298567850297183E+00 +step(A) = 48, err = 4.274049868592745E-01, h_new = 3.832289394395807E-03, n_yes = 30, n_no = 0, h*lambda = 3.306234916786975E+00 +step(A) = 49, err = 4.327411889343481E-01, h_new = 3.843931062320657E-03, n_yes = 31, n_no = 0, h*lambda = 3.308770400608201E+00 +step(A) = 50, err = 4.354784875719697E-01, h_new = 3.853389318091836E-03, n_yes = 32, n_no = 0, h*lambda = 3.305185144258135E+00 +step(A) = 51, err = 4.338299364635274E-01, h_new = 3.866337385609611E-03, n_yes = 33, n_no = 0, h*lambda = 3.299597220826402E+00 +step(A) = 52, err = 4.299482582563317E-01, h_new = 3.884671380738679E-03, n_yes = 34, n_no = 0, h*lambda = 3.296854674171938E+00 +step(A) = 53, err = 4.273038470190201E-01, h_new = 3.905783683190858E-03, n_yes = 35, n_no = 0, h*lambda = 3.298510698645197E+00 +step(A) = 54, err = 4.276130562870016E-01, h_new = 3.925558968985871E-03, n_yes = 36, n_no = 0, h*lambda = 3.302291579858145E+00 +step(A) = 55, err = 4.299022185899021E-01, h_new = 3.941969018536358E-03, n_yes = 37, n_no = 0, h*lambda = 3.304705624712692E+00 +step(A) = 56, err = 4.318278667904008E-01, h_new = 3.956286103098130E-03, n_yes = 38, n_no = 0, h*lambda = 3.304077414865688E+00 +step(A) = 57, err = 4.318055393701926E-01, h_new = 3.971399995522944E-03, n_yes = 39, n_no = 0, h*lambda = 3.301511376599596E+00 +step(A) = 58, err = 4.301433302687908E-01, h_new = 3.989178098074951E-03, n_yes = 40, n_no = 0, h*lambda = 3.299427218684884E+00 +step(A) = 59, err = 4.283980039979715E-01, h_new = 4.009187785387251E-03, n_yes = 41, n_no = 0, h*lambda = 3.299349782509462E+00 +step(A) = 60, err = 4.278528298765875E-01, h_new = 4.029514807417224E-03, n_yes = 42, n_no = 0, h*lambda = 3.300883653774126E+00 +step(A) = 61, err = 4.285629478103627E-01, h_new = 4.048597068577484E-03, n_yes = 43, n_no = 0, h*lambda = 3.302431999139033E+00 +step(A) = 62, err = 4.295742454421483E-01, h_new = 4.066409866813211E-03, n_yes = 44, n_no = 0, h*lambda = 3.302719490357139E+00 +step(A) = 63, err = 4.298863519096914E-01, h_new = 4.084181818148867E-03, n_yes = 45, n_no = 0, h*lambda = 3.301742927630545E+00 +step(A) = 64, err = 4.292626614580080E-01, h_new = 4.103163227947939E-03, n_yes = 46, n_no = 0, h*lambda = 3.300508019483630E+00 +step(A) = 65, err = 4.282627026618659E-01, h_new = 4.123628047631762E-03, n_yes = 47, n_no = 0, h*lambda = 3.300014559492133E+00 +step(A) = 66, err = 4.276241233632742E-01, h_new = 4.144859665351147E-03, n_yes = 48, n_no = 0, h*lambda = 3.300460431133917E+00 +step(A) = 67, err = 4.276369328988832E-01, h_new = 4.165930719970995E-03, n_yes = 49, n_no = 0, h*lambda = 3.301252668086431E+00 +step(A) = 68, err = 4.280092024191934E-01, h_new = 4.186494575650347E-03, n_yes = 50, n_no = 0, h*lambda = 3.301645687404880E+00 +step(A) = 69, err = 4.282204831856980E-01, h_new = 4.206953407913752E-03, n_yes = 51, n_no = 0, h*lambda = 3.301367652405577E+00 +step(A) = 70, err = 4.279837977260450E-01, h_new = 4.227993036622997E-03, n_yes = 52, n_no = 0, h*lambda = 3.300738226216514E+00 +step(A) = 71, err = 4.274266185960388E-01, h_new = 4.249985025858273E-03, n_yes = 53, n_no = 0, h*lambda = 3.300289463936676E+00 +step(A) = 72, err = 4.269015487139072E-01, h_new = 4.272761559292557E-03, n_yes = 54, n_no = 0, h*lambda = 3.300296852304890E+00 +step(A) = 73, err = 4.266570641946131E-01, h_new = 4.295867290392412E-03, n_yes = 55, n_no = 0, h*lambda = 3.300613643147131E+00 +step(A) = 74, err = 4.266619080988266E-01, h_new = 4.318990665846379E-03, n_yes = 56, n_no = 0, h*lambda = 3.300876408473557E+00 +step(A) = 75, err = 4.266887927136759E-01, h_new = 4.342193967280109E-03, n_yes = 57, n_no = 0, h*lambda = 3.300840114994215E+00 +step(A) = 76, err = 4.265362055845158E-01, h_new = 4.365798379295929E-03, n_yes = 58, n_no = 0, h*lambda = 3.300548680185818E+00 +step(A) = 77, err = 4.261836256266188E-01, h_new = 4.390085430569987E-03, n_yes = 59, n_no = 0, h*lambda = 3.300239345766098E+00 +step(A) = 78, err = 4.257697261781131E-01, h_new = 4.415090795345251E-03, n_yes = 60, n_no = 0, h*lambda = 3.300112913642135E+00 +step(A) = 79, err = 4.254479431181811E-01, h_new = 4.440636731223260E-03, n_yes = 61, n_no = 0, h*lambda = 3.300181318849340E+00 +step(A) = 80, err = 4.252609652718581E-01, h_new = 4.466529173764054E-03, n_yes = 62, n_no = 0, h*lambda = 3.300297748188524E+00 +step(A) = 81, err = 4.251299620505466E-01, h_new = 4.492728906892135E-03, n_yes = 63, n_no = 0, h*lambda = 3.300306923355792E+00 +step(A) = 82, err = 4.249426797540590E-01, h_new = 4.519365146406180E-03, n_yes = 64, n_no = 0, h*lambda = 3.300170619891046E+00 +step(A) = 83, err = 4.246496712819245E-01, h_new = 4.546612282542944E-03, n_yes = 65, n_no = 0, h*lambda = 3.299973567618228E+00 +step(A) = 84, err = 4.242902785324790E-01, h_new = 4.574555891799327E-03, n_yes = 66, n_no = 0, h*lambda = 3.299830092028462E+00 +step(A) = 85, err = 4.239422960828684E-01, h_new = 4.603157382739773E-03, n_yes = 67, n_no = 0, h*lambda = 3.299786765752271E+00 +step(A) = 86, err = 4.236516104086405E-01, h_new = 4.632325801040668E-03, n_yes = 68, n_no = 0, h*lambda = 3.299799497145615E+00 +step(A) = 87, err = 4.234021294817628E-01, h_new = 4.662017979380222E-03, n_yes = 69, n_no = 0, h*lambda = 3.299787665247157E+00 +step(A) = 88, err = 4.231413596534431E-01, h_new = 4.692281342522793E-03, n_yes = 70, n_no = 0, h*lambda = 3.299706001124972E+00 +step(A) = 89, err = 4.228295479463902E-01, h_new = 4.723216647529233E-03, n_yes = 71, n_no = 0, h*lambda = 3.299573225258155E+00 +step(A) = 90, err = 4.224683252337695E-01, h_new = 4.754906518256581E-03, n_yes = 72, n_no = 0, h*lambda = 3.299443688643050E+00 +step(A) = 91, err = 4.220903214026409E-01, h_new = 4.787373833359888E-03, n_yes = 73, n_no = 0, h*lambda = 3.299356575427028E+00 +step(A) = 92, err = 4.217262527483693E-01, h_new = 4.820597360350585E-03, n_yes = 74, n_no = 0, h*lambda = 3.299307852672099E+00 +step(A) = 93, err = 4.213804668434238E-01, h_new = 4.854560809515665E-03, n_yes = 75, n_no = 0, h*lambda = 3.299262534479615E+00 +step(A) = 94, err = 4.210326121938168E-01, h_new = 4.889289530465367E-03, n_yes = 76, n_no = 0, h*lambda = 3.299189029322131E+00 +step(A) = 95, err = 4.206585781344810E-01, h_new = 4.924848070602712E-03, n_yes = 77, n_no = 0, h*lambda = 3.299083582427861E+00 +step(A) = 96, err = 4.202499803472342E-01, h_new = 4.961308437665486E-03, n_yes = 78, n_no = 0, h*lambda = 3.298967423755382E+00 +step(A) = 97, err = 4.198169838136894E-01, h_new = 4.998720385114275E-03, n_yes = 79, n_no = 0, h*lambda = 3.298864619068732E+00 +step(A) = 98, err = 4.193757250575300E-01, h_new = 5.037107212994051E-03, n_yes = 80, n_no = 0, h*lambda = 3.298782313285313E+00 +step(A) = 99, err = 4.189335980658653E-01, h_new = 5.076485536102176E-03, n_yes = 81, n_no = 0, h*lambda = 3.298708596718419E+00 +step(A) = 100, err = 4.184845596159786E-01, h_new = 5.116888642345360E-03, n_yes = 82, n_no = 0, h*lambda = 3.298625996385327E+00 +step(A) = 101, err = 4.180160636406856E-01, h_new = 5.158374245905641E-03, n_yes = 83, n_no = 0, h*lambda = 3.298526576377524E+00 +step(A) = 102, err = 4.175196485353772E-01, h_new = 5.201013723191679E-03, n_yes = 84, n_no = 0, h*lambda = 3.298416291140438E+00 +step(A) = 103, err = 4.169959803365154E-01, h_new = 5.244875313946104E-03, n_yes = 85, n_no = 0, h*lambda = 3.298307381255416E+00 +step(A) = 104, err = 4.164515043329704E-01, h_new = 5.290016156004201E-03, n_yes = 86, n_no = 0, h*lambda = 3.298207372745942E+00 +step(A) = 105, err = 4.158911347361391E-01, h_new = 5.336488066005023E-03, n_yes = 87, n_no = 0, h*lambda = 3.298114148691819E+00 +step(A) = 106, err = 4.153136252593595E-01, h_new = 5.384350066412030E-03, n_yes = 88, n_no = 0, h*lambda = 3.298019744862072E+00 +step(A) = 107, err = 4.147127240190538E-01, h_new = 5.433676684227512E-03, n_yes = 89, n_no = 0, h*lambda = 3.297918025573869E+00 +step(A) = 108, err = 4.140819615591668E-01, h_new = 5.484556618665201E-03, n_yes = 90, n_no = 0, h*lambda = 3.297809325718020E+00 +step(A) = 109, err = 4.134184881004994E-01, h_new = 5.537085173716759E-03, n_yes = 91, n_no = 0, h*lambda = 3.297699007138310E+00 +step(A) = 110, err = 4.127230989644324E-01, h_new = 5.591358225306501E-03, n_yes = 92, n_no = 0, h*lambda = 3.297592357068396E+00 +step(A) = 111, err = 4.119972187559250E-01, h_new = 5.647472821223226E-03, n_yes = 93, n_no = 0, h*lambda = 3.297490657400588E+00 +step(A) = 112, err = 4.112398654527655E-01, h_new = 5.705533306168352E-03, n_yes = 94, n_no = 0, h*lambda = 3.297391313376692E+00 +step(A) = 113, err = 4.104469888420159E-01, h_new = 5.765657764230915E-03, n_yes = 95, n_no = 0, h*lambda = 3.297291101570885E+00 +step(A) = 114, err = 4.096131826642849E-01, h_new = 5.827980436381076E-03, n_yes = 96, n_no = 0, h*lambda = 3.297189317787030E+00 +step(A) = 115, err = 4.087338827099073E-01, h_new = 5.892649949216841E-03, n_yes = 97, n_no = 0, h*lambda = 3.297088360975718E+00 +step(A) = 116, err = 4.078061726986708E-01, h_new = 5.959826715838511E-03, n_yes = 98, n_no = 0, h*lambda = 3.296991843787721E+00 +step(A) = 117, err = 4.068278160422103E-01, h_new = 6.029683065905002E-03, n_yes = 99, n_no = 0, h*lambda = 3.296902269471554E+00 +step(A) = 118, err = 4.057956124289758E-01, h_new = 6.102407023818934E-03, n_yes = 100, n_no = 0, h*lambda = 3.296820238848189E+00 +step(A) = 119, err = 4.047044721781657E-01, h_new = 6.178207835896392E-03, n_yes = 101, n_no = 0, h*lambda = 3.296745534750657E+00 +step(A) = 120, err = 4.035477273747978E-01, h_new = 6.257320635684317E-03, n_yes = 102, n_no = 0, h*lambda = 3.296678879229760E+00 +step(A) = 121, err = 4.023180971211965E-01, h_new = 6.340009206902334E-03, n_yes = 103, n_no = 0, h*lambda = 3.296622888213925E+00 +step(A) = 122, err = 4.010083145398303E-01, h_new = 6.426567990759111E-03, n_yes = 104, n_no = 0, h*lambda = 3.296581706298546E+00 +step(A) = 123, err = 3.996108814206499E-01, h_new = 6.517325459700531E-03, n_yes = 105, n_no = 0, h*lambda = 3.296559976828275E+00 +step(A) = 124, err = 3.981171943064575E-01, h_new = 6.612650233258321E-03, n_yes = 106, n_no = 0, h*lambda = 3.296562218468241E+00 +step(A) = 125, err = 3.965166987559274E-01, h_new = 6.712959806132946E-03, n_yes = 107, n_no = 0, h*lambda = 3.296593170408349E+00 +step(A) = 126, err = 3.947965257609213E-01, h_new = 6.818730889980373E-03, n_yes = 108, n_no = 0, h*lambda = 3.296658832778643E+00 +step(A) = 127, err = 3.929415338960264E-01, h_new = 6.930510789481734E-03, n_yes = 109, n_no = 0, h*lambda = 3.296767499178149E+00 +step(A) = 128, err = 3.909343060343125E-01, h_new = 7.048930485777859E-03, n_yes = 110, n_no = 0, h*lambda = 3.296930315355223E+00 +step(A) = 129, err = 3.887546851840318E-01, h_new = 7.174721211834047E-03, n_yes = 111, n_no = 0, h*lambda = 3.297161486200941E+00 +step(A) = 130, err = 3.863787537767984E-01, h_new = 7.308736672001884E-03, n_yes = 112, n_no = 0, h*lambda = 3.297478671474467E+00 +step(A) = 131, err = 3.837774367234863E-01, h_new = 7.451982895312776E-03, n_yes = 113, n_no = 0, h*lambda = 3.297904088918098E+00 +step(A) = 132, err = 3.809149091912863E-01, h_new = 7.605657796489245E-03, n_yes = 114, n_no = 0, h*lambda = 3.298466526325578E+00 +step(A) = 133, err = 3.777467356032023E-01, h_new = 7.771203595907386E-03, n_yes = 115, n_no = 0, h*lambda = 3.299204218924662E+00 +step(A) = 134, err = 3.742173477295305E-01, h_new = 7.950377668672869E-03, n_yes = 116, n_no = 0, h*lambda = 3.300168645485718E+00 +step(A) = 135, err = 3.702562431534781E-01, h_new = 8.145351292695892E-03, n_yes = 117, n_no = 0, h*lambda = 3.301429746765951E+00 +step(A) = 136, err = 3.657721211397255E-01, h_new = 8.358851738847905E-03, n_yes = 118, n_no = 0, h*lambda = 3.303083723811001E+00 +step(A) = 137, err = 3.606438455263137E-01, h_new = 8.594373159051764E-03, n_yes = 119, n_no = 0, h*lambda = 3.305265376011864E+00 +step(A) = 138, err = 3.547062731120869E-01, h_new = 8.856500450818421E-03, n_yes = 120, n_no = 0, h*lambda = 3.308168166824125E+00 +step(A) = 139, err = 3.477270648774941E-01, h_new = 9.151428041710876E-03, n_yes = 121, n_no = 0, h*lambda = 3.312077654455424E+00 +step(A) = 140, err = 3.393664556521108E-01, h_new = 9.487836744273778E-03, n_yes = 122, n_no = 0, h*lambda = 3.317429288354494E+00 +step(A) = 141, err = 3.291024947823170E-01, h_new = 9.878481014846943E-03, n_yes = 123, n_no = 0, h*lambda = 3.324913729027767E+00 +step(A) = 142, err = 3.160801716816496E-01, h_new = 1.034333028834244E-02, n_yes = 124, n_no = 0, h*lambda = 3.335682132106649E+00 +step(A) = 143, err = 2.987727607034871E-01, h_new = 1.091658554971418E-02, n_yes = 125, n_no = 0, h*lambda = 3.351781701064660E+00 +step(A) = 144, err = 2.741028248940058E-01, h_new = 1.166534735210261E-02, n_yes = 126, n_no = 0, h*lambda = 3.377188017197576E+00 +step(A) = 145, err = 2.346239529639008E-01, h_new = 1.275538071971501E-02, n_yes = 127, n_no = 0, h*lambda = 3.420643301255753E+00 +step(A) = 146, err = 1.557890191666836E-01, h_new = 1.486002588342233E-02, n_yes = 128, n_no = 0, h*lambda = 3.504653543078997E+00 +step(A) = 147, err = 1.226099301345879E-01, h_new = 1.773838960162533E-02, n_yes = 129, n_no = 0, h*lambda = 3.804979086270134E+00 +step(R) = 148, err = 1.865508033855957E+00, h_new = 1.435890055771033E-02 +step(A) = 149, err = 5.567258694192786E-01, h_new = 1.312640152133710E-02, n_yes = 130, n_no = 0, h*lambda = 3.336672497886441E+00 +step(A) = 150, err = 4.497492134494654E-01, h_new = 1.321931282904350E-02, n_yes = 130, n_no = 1, h*lambda = 2.776090342499267E+00 +step(A) = 151, err = 2.958470302174228E-01, h_new = 1.417388091544419E-02, n_yes = 130, n_no = 2, h*lambda = 2.510927312768598E+00 +step(A) = 152, err = 2.808093537514360E-01, h_new = 1.507800943484761E-02, n_yes = 130, n_no = 3, h*lambda = 2.346913131339605E+00 +step(A) = 153, err = 2.677710048665810E-01, h_new = 1.613627102744548E-02, n_yes = 130, n_no = 4, h*lambda = 2.080888195172897E+00 +step(A) = 154, err = 2.265592527167412E-01, h_new = 1.773271868998401E-02, n_yes = 130, n_no = 5, h*lambda = 1.724597526684378E+00 +step(A) = 155, err = 1.595875690392127E-01, h_new = 2.054543146103107E-02, n_yes = 0, n_no = 6, h*lambda = 1.234987459111739E+00 +step(R) = 156, err = 2.116701634819634E+00, h_new = 1.627779728757842E-02 +step(A) = 157, err = 3.609774241119287E-01, h_new = 1.618775044353062E-02, n_yes = 0, n_no = 7, h*lambda = 9.990658037044617E-02 +step(R) = 158, err = 9.287356030077996E+00, h_new = 9.974413166910986E-03 +step(R) = 159, err = 1.287900027107479E+00, h_new = 8.599038580015742E-03 +step(A) = 160, err = 6.026470923255640E-01, h_new = 8.098058082990713E-03, n_yes = 0, n_no = 8, h*lambda = 3.967658571437078E-01 +step(A) = 161, err = 8.494145899462693E-01, h_new = 7.343034483239950E-03, n_yes = 0, n_no = 9, h*lambda = 1.828023413542616E+00 +step(R) = 162, err = 2.666288518776262E+07, h_new = 1.468606896647990E-03 +step(A) = 163, err = 3.230695729252238E-02, h_new = 2.353599329445250E-03, n_yes = 0, n_no = 10, h*lambda = 2.238398995261876E+00 +step(A) = 164, err = 3.163001418136029E-01, h_new = 1.401209326577277E-03, n_yes = 0, n_no = 11, h*lambda = 1.436600222728380E+00 +step(A) = 165, err = 6.786979901009902E-01, h_new = 1.286365809969477E-03, n_yes = 0, n_no = 12, h*lambda = 8.443327662565046E-01 +step(A) = 166, err = 8.209750533980206E-01, h_new = 1.178794137214864E-03, n_yes = 0, n_no = 13, h*lambda = 1.192678783953424E+00 +step(R) = 167, err = 1.576228468207524E+00, h_new = 9.819406027256731E-04 +step(A) = 168, err = 5.647098916181119E-01, h_new = 9.662524518774666E-04, n_yes = 0, n_no = 14, h*lambda = 9.651650874603465E-01 +step(A) = 169, err = 6.412908858819082E-01, h_new = 9.166568172828995E-04, n_yes = 0, n_no = 15, h*lambda = 9.687036860171175E-01 +step(A) = 170, err = 5.052997920519012E-01, h_new = 9.101817736226734E-04, n_yes = 0, n_no = 16, h*lambda = 9.239742915134123E-01 +step(A) = 171, err = 4.628627891108392E-01, h_new = 9.086271786242153E-04, n_yes = 0, n_no = 17, h*lambda = 9.192354929625264E-01 +step(A) = 172, err = 3.911348116716125E-01, h_new = 9.301453586857587E-04, n_yes = 0, n_no = 18, h*lambda = 9.179383411556022E-01 +step(A) = 173, err = 1.552925699871238E-01, h_new = 1.106600281753970E-03, n_yes = 0, n_no = 19, h*lambda = 9.393384125642390E-01 +step(A) = 174, err = 1.436442279291240E-01, h_new = 1.285702008915111E-03, n_yes = 0, n_no = 20, h*lambda = 1.116711101709752E+00 +step(A) = 175, err = 1.031704203427495E-01, h_new = 1.575324310476907E-03, n_yes = 0, n_no = 21, h*lambda = 1.296054764417737E+00 +step(A) = 176, err = 8.526097529863789E-02, h_new = 1.967556182223677E-03, n_yes = 0, n_no = 22, h*lambda = 1.585819982679173E+00 +step(A) = 177, err = 6.180674762160331E-02, h_new = 2.575867751692171E-03, n_yes = 0, n_no = 23, h*lambda = 1.977216120731085E+00 +step(A) = 178, err = 4.869859823188178E-02, h_new = 3.466807351534281E-03, n_yes = 0, n_no = 24, h*lambda = 2.582612950437261E+00 +step(A) = 179, err = 7.299927040235572E-02, h_new = 4.314283980060266E-03, n_yes = 1, n_no = 0, h*lambda = 3.465213353744876E+00 +step(A) = 180, err = 3.505180381832332E-01, h_new = 4.179104349180537E-03, n_yes = 2, n_no = 0, h*lambda = 4.295805939979648E+00 +step(R) = 181, err = 1.485964613741634E+00, h_new = 3.516286758966753E-03 +step(A) = 182, err = 5.349973884013645E-01, h_new = 3.375168250418784E-03, n_yes = 3, n_no = 0, h*lambda = 3.490303241670930E+00 +step(A) = 183, err = 5.803999025863498E-01, h_new = 3.249663232632744E-03, n_yes = 4, n_no = 0, h*lambda = 3.340113367882701E+00 +step(A) = 184, err = 4.892905749653268E-01, h_new = 3.231495793137459E-03, n_yes = 4, n_no = 1, h*lambda = 3.206498018436336E+00 +step(A) = 185, err = 3.892551199629235E-01, h_new = 3.318095958874776E-03, n_yes = 4, n_no = 2, h*lambda = 3.179243724117034E+00 +step(A) = 186, err = 3.547588633339274E-01, h_new = 3.429668708742143E-03, n_yes = 5, n_no = 0, h*lambda = 3.254612180464894E+00 +step(A) = 187, err = 3.866476014901526E-01, h_new = 3.480554241331193E-03, n_yes = 6, n_no = 0, h*lambda = 3.353554756147777E+00 +step(A) = 188, err = 4.540719782347893E-01, h_new = 3.448834301212362E-03, n_yes = 7, n_no = 0, h*lambda = 3.392504829836588E+00 +step(A) = 189, err = 4.976968146023749E-01, h_new = 3.386224911347087E-03, n_yes = 8, n_no = 0, h*lambda = 3.350968749644159E+00 +step(A) = 190, err = 4.794714119517586E-01, h_new = 3.358205296090499E-03, n_yes = 9, n_no = 0, h*lambda = 3.279879410231989E+00 +step(A) = 191, err = 4.298737629939639E-01, h_new = 3.387757385227220E-03, n_yes = 9, n_no = 1, h*lambda = 3.242633574387419E+00 +step(A) = 192, err = 3.974072197886699E-01, h_new = 3.448405822708895E-03, n_yes = 10, n_no = 0, h*lambda = 3.260875897285354E+00 +step(A) = 193, err = 4.004290084606149E-01, h_new = 3.494628133622083E-03, n_yes = 11, n_no = 0, h*lambda = 3.308587187956966E+00 +step(A) = 194, err = 4.292829756293552E-01, h_new = 3.500886866142602E-03, n_yes = 12, n_no = 0, h*lambda = 3.341980296964037E+00 +step(A) = 195, err = 4.573365638527151E-01, h_new = 3.479286767409603E-03, n_yes = 13, n_no = 0, h*lambda = 3.336964452467165E+00 +step(A) = 196, err = 4.607508183944088E-01, h_new = 3.462206219161891E-03, n_yes = 14, n_no = 0, h*lambda = 3.305496641644920E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 9.306509118845591E-01, ACCEPTED STEP = 189 +step(A) = 197, err = 4.414110580334484E-01, h_new = 3.471448611780695E-03, n_yes = 15, n_no = 0, h*lambda = 3.278481491393850E+00 +step(A) = 198, err = 4.205546755873946E-01, h_new = 3.503459967419302E-03, n_yes = 16, n_no = 0, h*lambda = 3.276376434523126E+00 +step(A) = 199, err = 4.145522690642272E-01, h_new = 3.537562218865710E-03, n_yes = 17, n_no = 0, h*lambda = 3.295523772606966E+00 +step(A) = 200, err = 4.245226729687935E-01, h_new = 3.555548581170433E-03, n_yes = 18, n_no = 0, h*lambda = 3.316314660236301E+00 +step(A) = 201, err = 4.396009549218003E-01, h_new = 3.555864434971921E-03, n_yes = 19, n_no = 0, h*lambda = 3.321766052385120E+00 +step(A) = 202, err = 4.465523480937836E-01, h_new = 3.551662957641811E-03, n_yes = 20, n_no = 0, h*lambda = 3.310637982341526E+00 +step(A) = 203, err = 4.411207989218947E-01, h_new = 3.557086033023634E-03, n_yes = 21, n_no = 0, h*lambda = 3.295316889242684E+00 +step(A) = 204, err = 4.303657217345063E-01, h_new = 3.575746941095130E-03, n_yes = 22, n_no = 0, h*lambda = 3.288891810451218E+00 +step(A) = 205, err = 4.239645584038416E-01, h_new = 3.600118268549008E-03, n_yes = 23, n_no = 0, h*lambda = 3.294558162340374E+00 +step(A) = 206, err = 4.258494625794100E-01, h_new = 3.619752878136143E-03, n_yes = 24, n_no = 0, h*lambda = 3.305257953939543E+00 +step(A) = 207, err = 4.327561969290570E-01, h_new = 3.630198018865545E-03, n_yes = 25, n_no = 0, h*lambda = 3.311391416744362E+00 +step(A) = 208, err = 4.381118801656300E-01, h_new = 3.635407523029907E-03, n_yes = 26, n_no = 0, h*lambda = 3.308973889358798E+00 +step(A) = 209, err = 4.378033206385368E-01, h_new = 3.642852387740103E-03, n_yes = 27, n_no = 0, h*lambda = 3.301702492669901E+00 +step(A) = 210, err = 4.331838707041977E-01, h_new = 3.656797891097270E-03, n_yes = 28, n_no = 0, h*lambda = 3.296381732091036E+00 +step(A) = 211, err = 4.288082266573860E-01, h_new = 3.675577885546121E-03, n_yes = 29, n_no = 0, h*lambda = 3.296813684138840E+00 +step(A) = 212, err = 4.279989888773814E-01, h_new = 3.694140400761719E-03, n_yes = 30, n_no = 0, h*lambda = 3.301420765469613E+00 +step(A) = 213, err = 4.305553889677722E-01, h_new = 3.708759583318981E-03, n_yes = 31, n_no = 0, h*lambda = 3.305633300473681E+00 +step(A) = 214, err = 4.336721825383330E-01, h_new = 3.719759708803042E-03, n_yes = 32, n_no = 0, h*lambda = 3.306143544614054E+00 +step(A) = 215, err = 4.346492908615610E-01, h_new = 3.730441485949745E-03, n_yes = 33, n_no = 0, h*lambda = 3.303290092263719E+00 +step(A) = 216, err = 4.330551307193308E-01, h_new = 3.743828608744013E-03, n_yes = 34, n_no = 0, h*lambda = 3.300029807124755E+00 +step(A) = 217, err = 4.306225375698375E-01, h_new = 3.760310844181213E-03, n_yes = 35, n_no = 0, h*lambda = 3.299020735245707E+00 +step(A) = 218, err = 4.293756907628447E-01, h_new = 3.777876527848858E-03, n_yes = 36, n_no = 0, h*lambda = 3.300566115806525E+00 +step(A) = 219, err = 4.299588438175121E-01, h_new = 3.794208535278070E-03, n_yes = 37, n_no = 0, h*lambda = 3.302870523433048E+00 +step(A) = 220, err = 4.314321127231581E-01, h_new = 3.808602621895280E-03, n_yes = 38, n_no = 0, h*lambda = 3.303907816512353E+00 +step(A) = 221, err = 4.323389026435731E-01, h_new = 3.822209930447735E-03, n_yes = 39, n_no = 0, h*lambda = 3.303085320258254E+00 +step(A) = 222, err = 4.319722586887214E-01, h_new = 3.836741351492421E-03, n_yes = 40, n_no = 0, h*lambda = 3.301419147276663E+00 +step(A) = 223, err = 4.307927176041086E-01, h_new = 3.852987912337170E-03, n_yes = 41, n_no = 0, h*lambda = 3.300385095949179E+00 +step(A) = 224, err = 4.298113342115492E-01, h_new = 3.870380413850879E-03, n_yes = 42, n_no = 0, h*lambda = 3.300644049791334E+00 +step(A) = 225, err = 4.296631557698509E-01, h_new = 3.887724647013937E-03, n_yes = 43, n_no = 0, h*lambda = 3.301686993646484E+00 +step(A) = 226, err = 4.301851388676324E-01, h_new = 3.904286808012671E-03, n_yes = 44, n_no = 0, h*lambda = 3.302485977632602E+00 +step(A) = 227, err = 4.307133054076446E-01, h_new = 3.920292122977307E-03, n_yes = 45, n_no = 0, h*lambda = 3.302422090464712E+00 +step(A) = 228, err = 4.307142557317240E-01, h_new = 3.936554777333275E-03, n_yes = 46, n_no = 0, h*lambda = 3.301694001215623E+00 +step(A) = 229, err = 4.301851746322752E-01, h_new = 3.953711297252749E-03, n_yes = 47, n_no = 0, h*lambda = 3.300988215440292E+00 +step(A) = 230, err = 4.295452210833650E-01, h_new = 3.971752422301366E-03, n_yes = 48, n_no = 0, h*lambda = 3.300828777040938E+00 +step(A) = 231, err = 4.292107952937533E-01, h_new = 3.990166572077914E-03, n_yes = 49, n_no = 0, h*lambda = 3.301193521192772E+00 +step(A) = 232, err = 4.292646244787926E-01, h_new = 4.008455751590245E-03, n_yes = 50, n_no = 0, h*lambda = 3.301646410466673E+00 +step(A) = 233, err = 4.294605789945160E-01, h_new = 4.026536547814079E-03, n_yes = 51, n_no = 0, h*lambda = 3.301771849193616E+00 +step(A) = 234, err = 4.294881484819751E-01, h_new = 4.044728598672159E-03, n_yes = 52, n_no = 0, h*lambda = 3.301501875902442E+00 +step(A) = 235, err = 4.292334976045317E-01, h_new = 4.063422951925993E-03, n_yes = 53, n_no = 0, h*lambda = 3.301097579500214E+00 +step(A) = 236, err = 4.288292849858888E-01, h_new = 4.082760731028665E-03, n_yes = 54, n_no = 0, h*lambda = 3.300871772816213E+00 +step(A) = 237, err = 4.284966709704843E-01, h_new = 4.102577075871451E-03, n_yes = 55, n_no = 0, h*lambda = 3.300928638830999E+00 +step(A) = 238, err = 4.283501725378235E-01, h_new = 4.122601297892653E-03, n_yes = 56, n_no = 0, h*lambda = 3.301124449621228E+00 +step(A) = 239, err = 4.283289637298356E-01, h_new = 4.142701463050688E-03, n_yes = 57, n_no = 0, h*lambda = 3.301234132453863E+00 +step(A) = 240, err = 4.282820596342666E-01, h_new = 4.162968884373361E-03, n_yes = 58, n_no = 0, h*lambda = 3.301147770350358E+00 +step(A) = 241, err = 4.281074167918837E-01, h_new = 4.183607199895807E-03, n_yes = 59, n_no = 0, h*lambda = 3.300934116182215E+00 +step(A) = 242, err = 4.278229899033850E-01, h_new = 4.204754278001570E-03, n_yes = 60, n_no = 0, h*lambda = 3.300748273611485E+00 +step(A) = 243, err = 4.275270575767048E-01, h_new = 4.226393038144430E-03, n_yes = 61, n_no = 0, h*lambda = 3.300689871481195E+00 +step(A) = 244, err = 4.273027857197043E-01, h_new = 4.248404526254815E-03, n_yes = 62, n_no = 0, h*lambda = 3.300735255137595E+00 +step(A) = 245, err = 4.271550363189458E-01, h_new = 4.270692093308264E-03, n_yes = 63, n_no = 0, h*lambda = 3.300782193625966E+00 +step(A) = 246, err = 4.270228587457322E-01, h_new = 4.293263069108353E-03, n_yes = 64, n_no = 0, h*lambda = 3.300747843562232E+00 +step(A) = 247, err = 4.268417375912550E-01, h_new = 4.316211182244281E-03, n_yes = 65, n_no = 0, h*lambda = 3.300630716059705E+00 +step(A) = 248, err = 4.265956213794925E-01, h_new = 4.339633801499298E-03, n_yes = 66, n_no = 0, h*lambda = 3.300494780816980E+00 +step(A) = 249, err = 4.263193737172365E-01, h_new = 4.363563362692446E-03, n_yes = 67, n_no = 0, h*lambda = 3.300404709203992E+00 +step(A) = 250, err = 4.260600881589757E-01, h_new = 4.387964990585264E-03, n_yes = 68, n_no = 0, h*lambda = 3.300374150266904E+00 +step(A) = 251, err = 4.258368123727622E-01, h_new = 4.412788910191768E-03, n_yes = 69, n_no = 0, h*lambda = 3.300365536963737E+00 +step(A) = 252, err = 4.256311074018860E-01, h_new = 4.438024743324536E-03, n_yes = 70, n_no = 0, h*lambda = 3.300330755475663E+00 +step(A) = 253, err = 4.254094374484656E-01, h_new = 4.463713918290826E-03, n_yes = 71, n_no = 0, h*lambda = 3.300251688462732E+00 +step(A) = 254, err = 4.251528637700765E-01, h_new = 4.489918712614340E-03, n_yes = 72, n_no = 0, h*lambda = 3.300148342366018E+00 +step(A) = 255, err = 4.248688506904036E-01, h_new = 4.516681435733906E-03, n_yes = 73, n_no = 0, h*lambda = 3.300054879683316E+00 +step(A) = 256, err = 4.245795292941795E-01, h_new = 4.544008415282716E-03, n_yes = 74, n_no = 0, h*lambda = 3.299989397776667E+00 +step(A) = 257, err = 4.243007359500475E-01, h_new = 4.571886655869392E-03, n_yes = 75, n_no = 0, h*lambda = 3.299942855104345E+00 +step(A) = 258, err = 4.240304753675670E-01, h_new = 4.600313341654582E-03, n_yes = 76, n_no = 0, h*lambda = 3.299891949477974E+00 +step(A) = 259, err = 4.237540025415181E-01, h_new = 4.629312064914113E-03, n_yes = 77, n_no = 0, h*lambda = 3.299820658193994E+00 +step(A) = 260, err = 4.234579704996791E-01, h_new = 4.658925511089955E-03, n_yes = 78, n_no = 0, h*lambda = 3.299731457984167E+00 +step(A) = 261, err = 4.231404670609210E-01, h_new = 4.689195217083679E-03, n_yes = 79, n_no = 0, h*lambda = 3.299639449519989E+00 +step(A) = 262, err = 4.228098096408096E-01, h_new = 4.720147236695432E-03, n_yes = 80, n_no = 0, h*lambda = 3.299557656336667E+00 +step(A) = 263, err = 4.224754266134728E-01, h_new = 4.751794061989775E-03, n_yes = 81, n_no = 0, h*lambda = 3.299487064891137E+00 +step(A) = 264, err = 4.221397572251011E-01, h_new = 4.784148090628258E-03, n_yes = 82, n_no = 0, h*lambda = 3.299418345988125E+00 +step(A) = 265, err = 4.217974025195748E-01, h_new = 4.817233646248816E-03, n_yes = 83, n_no = 0, h*lambda = 3.299341434461201E+00 +step(A) = 266, err = 4.214406700734891E-01, h_new = 4.851088314634407E-03, n_yes = 84, n_no = 0, h*lambda = 3.299253687082798E+00 +step(A) = 267, err = 4.210657477187505E-01, h_new = 4.885754748735579E-03, n_yes = 85, n_no = 0, h*lambda = 3.299160419086709E+00 +step(A) = 268, err = 4.206744564359745E-01, h_new = 4.921271494404199E-03, n_yes = 86, n_no = 0, h*lambda = 3.299068991664824E+00 +step(A) = 269, err = 4.202711170796404E-01, h_new = 4.957670479628542E-03, n_yes = 87, n_no = 0, h*lambda = 3.298982513508255E+00 +step(A) = 270, err = 4.198580540941697E-01, h_new = 4.994981975273635E-03, n_yes = 88, n_no = 0, h*lambda = 3.298898254205454E+00 +step(A) = 271, err = 4.194335995254261E-01, h_new = 5.033241715660592E-03, n_yes = 89, n_no = 0, h*lambda = 3.298811045040141E+00 +step(A) = 272, err = 4.189936537310915E-01, h_new = 5.072494208865975E-03, n_yes = 90, n_no = 0, h*lambda = 3.298717913852740E+00 +step(A) = 273, err = 4.185348058698909E-01, h_new = 5.112790510439968E-03, n_yes = 91, n_no = 0, h*lambda = 3.298620005928226E+00 +step(A) = 274, err = 4.180561978345175E-01, h_new = 5.154183518164869E-03, n_yes = 92, n_no = 0, h*lambda = 3.298520832099531E+00 +step(A) = 275, err = 4.175589093288529E-01, h_new = 5.196725241729564E-03, n_yes = 93, n_no = 0, h*lambda = 3.298423010403305E+00 +step(A) = 276, err = 4.170438715311455E-01, h_new = 5.240468066166896E-03, n_yes = 94, n_no = 0, h*lambda = 3.298326412486860E+00 +step(A) = 277, err = 4.165102735702943E-01, h_new = 5.285468464511332E-03, n_yes = 95, n_no = 0, h*lambda = 3.298228897592729E+00 +step(A) = 278, err = 4.159556178376821E-01, h_new = 5.331789991909397E-03, n_yes = 96, n_no = 0, h*lambda = 3.298128510128896E+00 +step(A) = 279, err = 4.153770237894199E-01, h_new = 5.379503623610917E-03, n_yes = 97, n_no = 0, h*lambda = 3.298025088502002E+00 +step(A) = 280, err = 4.147724711094216E-01, h_new = 5.428686039232078E-03, n_yes = 98, n_no = 0, h*lambda = 3.297920132091420E+00 +step(A) = 281, err = 4.141409969553875E-01, h_new = 5.479418021444618E-03, n_yes = 99, n_no = 0, h*lambda = 3.297815401152584E+00 +step(A) = 282, err = 4.134819002793722E-01, h_new = 5.531784671825134E-03, n_yes = 100, n_no = 0, h*lambda = 3.297711633507323E+00 +step(A) = 283, err = 4.127937823693175E-01, h_new = 5.585877421315124E-03, n_yes = 101, n_no = 0, h*lambda = 3.297608380072957E+00 +step(A) = 284, err = 4.120742054532967E-01, h_new = 5.641796452119404E-03, n_yes = 102, n_no = 0, h*lambda = 3.297504863094436E+00 +step(A) = 285, err = 4.113200925766133E-01, h_new = 5.699652167295751E-03, n_yes = 103, n_no = 0, h*lambda = 3.297400969642427E+00 +step(A) = 286, err = 4.105283746583379E-01, h_new = 5.759565460903200E-03, n_yes = 104, n_no = 0, h*lambda = 3.297297572992608E+00 +step(A) = 287, err = 4.096962958683139E-01, h_new = 5.821667654198976E-03, n_yes = 105, n_no = 0, h*lambda = 3.297196079678740E+00 +step(A) = 288, err = 4.088211805521913E-01, h_new = 5.886101189534363E-03, n_yes = 106, n_no = 0, h*lambda = 3.297097739265874E+00 +step(A) = 289, err = 4.078999292818465E-01, h_new = 5.953021509568919E-03, n_yes = 107, n_no = 0, h*lambda = 3.297003333553721E+00 +step(A) = 290, err = 4.069286643369742E-01, h_new = 6.022599701667102E-03, n_yes = 108, n_no = 0, h*lambda = 3.296913436258292E+00 +step(A) = 291, err = 4.059027307924134E-01, h_new = 6.095025172833385E-03, n_yes = 109, n_no = 0, h*lambda = 3.296828947091119E+00 +step(A) = 292, err = 4.048169258521827E-01, h_new = 6.170507994935453E-03, n_yes = 110, n_no = 0, h*lambda = 3.296751455234362E+00 +step(A) = 293, err = 4.036656653307505E-01, h_new = 6.249281203921879E-03, n_yes = 111, n_no = 0, h*lambda = 3.296683224161405E+00 +step(A) = 294, err = 4.024428954342126E-01, h_new = 6.331603700550962E-03, n_yes = 112, n_no = 0, h*lambda = 3.296626944717163E+00 +step(A) = 295, err = 4.011417849609907E-01, h_new = 6.417764266934729E-03, n_yes = 113, n_no = 0, h*lambda = 3.296585571191480E+00 +step(A) = 296, err = 3.997543801409948E-01, h_new = 6.508086807558066E-03, n_yes = 114, n_no = 0, h*lambda = 3.296562443276060E+00 +step(A) = 297, err = 3.982713709435165E-01, h_new = 6.602936651121051E-03, n_yes = 115, n_no = 0, h*lambda = 3.296561653744635E+00 +step(A) = 298, err = 3.966819670436657E-01, h_new = 6.702727844174185E-03, n_yes = 116, n_no = 0, h*lambda = 3.296588469298182E+00 +step(A) = 299, err = 3.949737553394205E-01, h_new = 6.807931743832415E-03, n_yes = 117, n_no = 0, h*lambda = 3.296649654825138E+00 +step(A) = 300, err = 3.931324002241851E-01, h_new = 6.919087605638564E-03, n_yes = 118, n_no = 0, h*lambda = 3.296753719326704E+00 +step(A) = 301, err = 3.911411317660657E-01, h_new = 7.036816069000745E-03, n_yes = 119, n_no = 0, h*lambda = 3.296911242803488E+00 +step(A) = 302, err = 3.889800524918311E-01, h_new = 7.161836500789414E-03, n_yes = 120, n_no = 0, h*lambda = 3.297135461658267E+00 +step(A) = 303, err = 3.866253029078894E-01, h_new = 7.294989285961767E-03, n_yes = 121, n_no = 0, h*lambda = 3.297443211237453E+00 +step(A) = 304, err = 3.840480530727116E-01, h_new = 7.437264588283261E-03, n_yes = 122, n_no = 0, h*lambda = 3.297856252396248E+00 +step(A) = 305, err = 3.812131839835431E-01, h_new = 7.589839976134142E-03, n_yes = 123, n_no = 0, h*lambda = 3.298403033530250E+00 +step(A) = 306, err = 3.780774391397880E-01, h_new = 7.754130675267412E-03, n_yes = 124, n_no = 0, h*lambda = 3.299121075777927E+00 +step(A) = 307, err = 3.745867642474011E-01, h_new = 7.931858222387088E-03, n_yes = 125, n_no = 0, h*lambda = 3.300060376573135E+00 +step(A) = 308, err = 3.706724549970032E-01, h_new = 8.125146419271706E-03, n_yes = 126, n_no = 0, h*lambda = 3.301288478615848E+00 +step(A) = 309, err = 3.662455075648296E-01, h_new = 8.336658774668522E-03, n_yes = 127, n_no = 0, h*lambda = 3.302898201714379E+00 +step(A) = 310, err = 3.611880979146040E-01, h_new = 8.569801139875793E-03, n_yes = 128, n_no = 0, h*lambda = 3.305019656711766E+00 +step(A) = 311, err = 3.553402125989959E-01, h_new = 8.829031172529684E-03, n_yes = 129, n_no = 0, h*lambda = 3.307839385838009E+00 +step(A) = 312, err = 3.484776836089697E-01, h_new = 9.120351759981103E-03, n_yes = 130, n_no = 0, h*lambda = 3.311631938627857E+00 +step(A) = 313, err = 3.402741534154088E-01, h_new = 9.452140609361080E-03, n_yes = 131, n_no = 0, h*lambda = 3.316814291708847E+00 +step(A) = 314, err = 3.302309183287216E-01, h_new = 9.836641122382020E-03, n_yes = 132, n_no = 0, h*lambda = 3.324044700872036E+00 +step(A) = 315, err = 3.175367833695593E-01, h_new = 1.029288353523294E-02, n_yes = 133, n_no = 0, h*lambda = 3.334414182586814E+00 +step(A) = 316, err = 3.007574192158975E-01, h_new = 1.085311863757283E-02, n_yes = 134, n_no = 0, h*lambda = 3.349848960294455E+00 +step(A) = 317, err = 2.770425480122097E-01, h_new = 1.157958015831396E-02, n_yes = 135, n_no = 0, h*lambda = 3.374052218152749E+00 +step(A) = 318, err = 2.396474186044322E-01, h_new = 1.262146633905467E-02, n_yes = 136, n_no = 0, h*lambda = 3.415050308835077E+00 +step(A) = 319, err = 1.672169330529275E-01, h_new = 1.454044138080664E-02, n_yes = 137, n_no = 0, h*lambda = 3.493245381385295E+00 +step(A) = 320, err = 6.547522350678368E-02, h_new = 1.936503210159936E-02, n_yes = 138, n_no = 0, h*lambda = 3.779558822622686E+00 +step(R) = 321, err = 2.758072167957366E+00, h_new = 1.466755665209359E-02 +step(A) = 322, err = 5.544936042011636E-01, h_new = 1.308521766710530E-02, n_yes = 139, n_no = 0, h*lambda = 3.439844267474361E+00 +step(A) = 323, err = 4.608234807060616E-01, h_new = 1.312134759460148E-02, n_yes = 139, n_no = 1, h*lambda = 2.796465720746335E+00 +step(A) = 324, err = 2.969699612322509E-01, h_new = 1.407347043447111E-02, n_yes = 139, n_no = 2, h*lambda = 2.524954751933256E+00 +step(A) = 325, err = 2.801906534181062E-01, h_new = 1.497907847144951E-02, n_yes = 139, n_no = 3, h*lambda = 2.369860469530868E+00 +step(A) = 326, err = 2.705027714409757E-01, h_new = 1.600134757534437E-02, n_yes = 139, n_no = 4, h*lambda = 2.114647776148836E+00 +step(A) = 327, err = 2.322671428940118E-01, h_new = 1.751733417009518E-02, n_yes = 139, n_no = 5, h*lambda = 1.767476442453646E+00 +step(A) = 328, err = 1.694881217536604E-01, h_new = 2.010927253686414E-02, n_yes = 0, n_no = 6, h*lambda = 1.306442795389962E+00 +step(R) = 329, err = 1.084461510147079E+00, h_new = 1.785058548755503E-02 +step(A) = 330, err = 4.164041221376866E-01, h_new = 1.736772732187644E-02, n_yes = 0, n_no = 7, h*lambda = 1.821084854907908E-01 +step(R) = 331, err = 5.013796319294077E+00, h_new = 1.188385008615472E-02 +step(R) = 332, err = 2.939269589813126E+00, h_new = 8.904283366712418E-03 +step(A) = 333, err = 6.344689183282852E-01, h_new = 8.360116372862887E-03, n_yes = 0, n_no = 8, h*lambda = 4.999577635284350E-01 +step(R) = 334, err = 1.506978358294373E+00, h_new = 7.017406578740081E-03 +step(R) = 335, err = 1.507078084983740E+00, h_new = 5.890281776063439E-03 +step(R) = 336, err = 1.056204469574164E+00, h_new = 5.252201992362025E-03 +step(A) = 337, err = 7.013961932938925E-01, h_new = 4.930223016394774E-03, n_yes = 0, n_no = 9, h*lambda = 4.458951240552822E-01 +step(R) = 338, err = 1.368003160182285E+01, h_new = 2.844292572318407E-03 +step(A) = 339, err = 2.154297042179343E-01, h_new = 3.276373188408001E-03, n_yes = 0, n_no = 10, h*lambda = 5.781008124062809E-01 +step(A) = 340, err = 6.517791035130871E-01, h_new = 2.589118491593048E-03, n_yes = 1, n_no = 0, h*lambda = 9.662911867304125E+00 +step(R) = 341, err = 9.363066043127942E+00, h_new = 1.593137869779816E-03 +step(R) = 342, err = 1.103967583566059E+00, h_new = 1.409916185027988E-03 +step(A) = 343, err = 5.792252937560954E-01, h_new = 1.368723565463238E-03, n_yes = 1, n_no = 1, h*lambda = 6.116552865976006E-01 +step(A) = 344, err = 6.875690543357760E-01, h_new = 1.284483056949639E-03, n_yes = 1, n_no = 2, h*lambda = 1.225537118237090E+00 +step(R) = 345, err = 2.533668994973810E+00, h_new = 9.870369488878133E-04 +step(A) = 346, err = 5.634250690081378E-01, h_new = 9.647758513977807E-04, n_yes = 1, n_no = 3, h*lambda = 9.696329291316175E-01 +step(A) = 347, err = 6.397646579904136E-01, h_new = 9.155434055817495E-04, n_yes = 1, n_no = 4, h*lambda = 9.695144007443871E-01 +step(A) = 348, err = 5.120257578326355E-01, h_new = 9.069485495243634E-04, n_yes = 1, n_no = 5, h*lambda = 9.258268721120645E-01 +step(A) = 349, err = 4.684576636511742E-01, h_new = 9.040300635595413E-04, n_yes = 0, n_no = 6, h*lambda = 9.192261126382525E-01 +step(A) = 350, err = 3.992343733354638E-01, h_new = 9.226637306870890E-04, n_yes = 0, n_no = 7, h*lambda = 9.166675883653799E-01 +step(A) = 351, err = 1.739001318627061E-01, h_new = 1.077665775299492E-03, n_yes = 0, n_no = 8, h*lambda = 9.352847703097384E-01 +step(A) = 352, err = 1.450216591931376E-01, h_new = 1.255726353499400E-03, n_yes = 0, n_no = 9, h*lambda = 1.091654665468176E+00 +step(A) = 353, err = 1.079829569862108E-01, h_new = 1.527300332308325E-03, n_yes = 0, n_no = 10, h*lambda = 1.270717098147814E+00 +step(A) = 354, err = 8.757978699231204E-02, h_new = 1.902359116380817E-03, n_yes = 0, n_no = 11, h*lambda = 1.543482436588028E+00 +step(A) = 355, err = 6.456233631672992E-02, h_new = 2.474769177314450E-03, n_yes = 0, n_no = 12, h*lambda = 1.919294523623212E+00 +step(A) = 356, err = 4.949968416405767E-02, h_new = 3.327315357597040E-03, n_yes = 0, n_no = 13, h*lambda = 2.491356651687152E+00 +step(A) = 357, err = 6.438796693784986E-02, h_new = 4.232761223989395E-03, n_yes = 1, n_no = 0, h*lambda = 3.339793883877691E+00 +step(A) = 358, err = 2.797730075652627E-01, h_new = 4.238981097611739E-03, n_yes = 2, n_no = 0, h*lambda = 4.232766260052859E+00 +step(R) = 359, err = 1.316524838278028E+00, h_new = 3.640835437843951E-03 +step(A) = 360, err = 5.341844412927746E-01, h_new = 3.464242470641854E-03, n_yes = 3, n_no = 0, h*lambda = 3.629137340664018E+00 +step(A) = 361, err = 7.018131539846458E-01, h_new = 3.229243431640567E-03, n_yes = 4, n_no = 0, h*lambda = 3.442495298897918E+00 +step(A) = 362, err = 5.894036582809767E-01, h_new = 3.134887599028365E-03, n_yes = 4, n_no = 1, h*lambda = 3.199693006924603E+00 +step(A) = 363, err = 4.026123115995935E-01, h_new = 3.224409542058257E-03, n_yes = 4, n_no = 2, h*lambda = 3.097414849948199E+00 +step(A) = 364, err = 3.179969364962871E-01, h_new = 3.399979433853157E-03, n_yes = 4, n_no = 3, h*lambda = 3.176578537871659E+00 +step(A) = 365, err = 3.367762714741842E-01, h_new = 3.516961908370304E-03, n_yes = 5, n_no = 0, h*lambda = 3.339234748253349E+00 +step(A) = 366, err = 4.309276423004997E-01, h_new = 3.496673589743067E-03, n_yes = 6, n_no = 0, h*lambda = 3.443108463530480E+00 +step(A) = 367, err = 5.277919916031375E-01, h_new = 3.391994204435137E-03, n_yes = 7, n_no = 0, h*lambda = 3.412353949777300E+00 +step(A) = 368, err = 5.296304036373101E-01, h_new = 3.315283929816004E-03, n_yes = 8, n_no = 0, h*lambda = 3.299927072669767E+00 +step(A) = 369, err = 4.526156127179511E-01, h_new = 3.328496725453776E-03, n_yes = 8, n_no = 1, h*lambda = 3.215457491431500E+00 +step(A) = 370, err = 3.869624821929510E-01, h_new = 3.410484801373498E-03, n_yes = 8, n_no = 2, h*lambda = 3.218340142087190E+00 +step(A) = 371, err = 3.744993934352231E-01, h_new = 3.492035838153027E-03, n_yes = 9, n_no = 0, h*lambda = 3.287189159799703E+00 +step(A) = 372, err = 4.100194386292366E-01, h_new = 3.516272226629501E-03, n_yes = 10, n_no = 0, h*lambda = 3.354865484297375E+00 +step(A) = 373, err = 4.608766850410994E-01, h_new = 3.483595911385081E-03, n_yes = 11, n_no = 0, h*lambda = 3.367069048238239E+00 +step(A) = 374, err = 4.816281309827945E-01, h_new = 3.441538699119439E-03, n_yes = 12, n_no = 0, h*lambda = 3.324891144948532E+00 +step(A) = 375, err = 4.584277456650542E-01, h_new = 3.434690339406016E-03, n_yes = 13, n_no = 0, h*lambda = 3.274104202326166E+00 +step(A) = 376, err = 4.217010246797815E-01, h_new = 3.470005332842031E-03, n_yes = 14, n_no = 0, h*lambda = 3.256970425632885E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 1.809882994363210E+00, ACCEPTED STEP = 357 +step(A) = 377, err = 4.035671505315741E-01, h_new = 3.520198475310685E-03, n_yes = 15, n_no = 0, h*lambda = 3.279612978039768E+00 +step(A) = 378, err = 4.124998511640817E-01, h_new = 3.551601632326704E-03, n_yes = 16, n_no = 0, h*lambda = 3.315887769755501E+00 +step(A) = 379, err = 4.364865333550677E-01, h_new = 3.552128464247225E-03, n_yes = 17, n_no = 0, h*lambda = 3.334099100811456E+00 +step(A) = 380, err = 4.537348839176653E-01, h_new = 3.537314207099786E-03, n_yes = 18, n_no = 0, h*lambda = 3.323211423179065E+00 +step(A) = 381, err = 4.507776443101032E-01, h_new = 3.531950690399435E-03, n_yes = 19, n_no = 0, h*lambda = 3.298050238122626E+00 +step(A) = 382, err = 4.342894091797004E-01, h_new = 3.548078055168615E-03, n_yes = 20, n_no = 0, h*lambda = 3.281767755346524E+00 +step(A) = 383, err = 4.206542956357074E-01, h_new = 3.578322989303324E-03, n_yes = 21, n_no = 0, h*lambda = 3.285356720287079E+00 +step(A) = 384, err = 4.196258972159727E-01, h_new = 3.605723937722158E-03, n_yes = 22, n_no = 0, h*lambda = 3.301762969346105E+00 +step(A) = 385, err = 4.292224611775394E-01, h_new = 3.619040631088908E-03, n_yes = 23, n_no = 0, h*lambda = 3.315260929917007E+00 +step(A) = 386, err = 4.397641151448633E-01, h_new = 3.620728012932406E-03, n_yes = 24, n_no = 0, h*lambda = 3.315622571335918E+00 +step(A) = 387, err = 4.424148223975358E-01, h_new = 3.622231135715686E-03, n_yes = 25, n_no = 0, h*lambda = 3.305262547418670E+00 +step(A) = 388, err = 4.365197194343846E-01, h_new = 3.632881216516610E-03, n_yes = 26, n_no = 0, h*lambda = 3.294705192440506E+00 +step(A) = 389, err = 4.286360496827978E-01, h_new = 3.652908418796396E-03, n_yes = 27, n_no = 0, h*lambda = 3.292379763151696E+00 +step(A) = 390, err = 4.253576036256687E-01, h_new = 3.675163184205102E-03, n_yes = 28, n_no = 0, h*lambda = 3.298373393204342E+00 +step(A) = 391, err = 4.281672664981239E-01, h_new = 3.692283301841689E-03, n_yes = 29, n_no = 0, h*lambda = 3.306152666725508E+00 +step(A) = 392, err = 4.335203958454018E-01, h_new = 3.702631088354470E-03, n_yes = 30, n_no = 0, h*lambda = 3.309111987348912E+00 +step(A) = 393, err = 4.365379891513724E-01, h_new = 3.710475656903107E-03, n_yes = 31, n_no = 0, h*lambda = 3.305861487497835E+00 +step(A) = 394, err = 4.351678259492997E-01, h_new = 3.721356918231456E-03, n_yes = 32, n_no = 0, h*lambda = 3.300274009212339E+00 +step(A) = 395, err = 4.313533339228802E-01, h_new = 3.737390428949175E-03, n_yes = 33, n_no = 0, h*lambda = 3.297272979759672E+00 +step(A) = 396, err = 4.285761795089301E-01, h_new = 3.756293683257723E-03, n_yes = 34, n_no = 0, h*lambda = 3.298677130891020E+00 +step(A) = 397, err = 4.287190833223712E-01, h_new = 3.774103377894652E-03, n_yes = 35, n_no = 0, h*lambda = 3.302416479114071E+00 +step(A) = 398, err = 4.309643259121818E-01, h_new = 3.788682301898346E-03, n_yes = 36, n_no = 0, h*lambda = 3.304993020455458E+00 +step(A) = 399, err = 4.329973638391437E-01, h_new = 3.801069921998613E-03, n_yes = 37, n_no = 0, h*lambda = 3.304563353713566E+00 +step(A) = 400, err = 4.331357163463204E-01, h_new = 3.814008869783408E-03, n_yes = 38, n_no = 0, h*lambda = 3.302070284168578E+00 +step(A) = 401, err = 4.315662949192660E-01, h_new = 3.829403146181003E-03, n_yes = 39, n_no = 0, h*lambda = 3.299906818493278E+00 +step(A) = 402, err = 4.298065745569287E-01, h_new = 3.846972490639721E-03, n_yes = 40, n_no = 0, h*lambda = 3.299698514897062E+00 +step(A) = 403, err = 4.291955448638118E-01, h_new = 3.864925504721233E-03, n_yes = 41, n_no = 0, h*lambda = 3.301170400616469E+00 +step(A) = 404, err = 4.298730042495896E-01, h_new = 3.881700430529659E-03, n_yes = 42, n_no = 0, h*lambda = 3.302765846452712E+00 +step(A) = 405, err = 4.309260669391629E-01, h_new = 3.897172792657341E-03, n_yes = 43, n_no = 0, h*lambda = 3.303154342426835E+00 +step(A) = 406, err = 4.313284316808457E-01, h_new = 3.912468980686372E-03, n_yes = 44, n_no = 0, h*lambda = 3.302246150455677E+00 +step(A) = 407, err = 4.307845084316195E-01, h_new = 3.928814528531829E-03, n_yes = 45, n_no = 0, h*lambda = 3.301004969889356E+00 +step(A) = 408, err = 4.298165423296664E-01, h_new = 3.946538175855121E-03, n_yes = 46, n_no = 0, h*lambda = 3.300457272966123E+00 +step(A) = 409, err = 4.291720917131202E-01, h_new = 3.964996354060301E-03, n_yes = 47, n_no = 0, h*lambda = 3.300860336246080E+00 +step(A) = 410, err = 4.291798750078695E-01, h_new = 3.983289498678929E-03, n_yes = 48, n_no = 0, h*lambda = 3.301659001677701E+00 +step(A) = 411, err = 4.295785616112979E-01, h_new = 4.001038338064565E-03, n_yes = 49, n_no = 0, h*lambda = 3.302098366435897E+00 +step(A) = 412, err = 4.298476998641972E-01, h_new = 4.018587629619069E-03, n_yes = 50, n_no = 0, h*lambda = 3.301867531751115E+00 +step(A) = 413, err = 4.296760459011817E-01, h_new = 4.036589093767548E-03, n_yes = 51, n_no = 0, h*lambda = 3.301254848795535E+00 +step(A) = 414, err = 4.291671457160097E-01, h_new = 4.055423356776763E-03, n_yes = 52, n_no = 0, h*lambda = 3.300792187767005E+00 +step(A) = 415, err = 4.286692624816428E-01, h_new = 4.074956413623288E-03, n_yes = 53, n_no = 0, h*lambda = 3.300779491762595E+00 +step(A) = 416, err = 4.284453888853878E-01, h_new = 4.094757061904322E-03, n_yes = 54, n_no = 0, h*lambda = 3.301094355125954E+00 +step(A) = 417, err = 4.284820221711864E-01, h_new = 4.114508142340670E-03, n_yes = 55, n_no = 0, h*lambda = 3.301378208179379E+00 +step(A) = 418, err = 4.285581027439317E-01, h_new = 4.134243848943203E-03, n_yes = 56, n_no = 0, h*lambda = 3.301371663799155E+00 +step(A) = 419, err = 4.284643391197592E-01, h_new = 4.154258248986627E-03, n_yes = 57, n_no = 0, h*lambda = 3.301100608811631E+00 +step(A) = 420, err = 4.281676627050557E-01, h_new = 4.174824569557561E-03, n_yes = 58, n_no = 0, h*lambda = 3.300795749714672E+00 +step(A) = 421, err = 4.278005791270703E-01, h_new = 4.195988237915426E-03, n_yes = 59, n_no = 0, h*lambda = 3.300665609575754E+00 +step(A) = 422, err = 4.275203746058943E-01, h_new = 4.217584256107079E-03, n_yes = 60, n_no = 0, h*lambda = 3.300734901043207E+00 +step(A) = 423, err = 4.273784348063611E-01, h_new = 4.239419633722540E-03, n_yes = 61, n_no = 0, h*lambda = 3.300863388402195E+00 +step(A) = 424, err = 4.273015686254796E-01, h_new = 4.261441761630899E-03, n_yes = 62, n_no = 0, h*lambda = 3.300892260933561E+00 +step(A) = 425, err = 4.271763403967118E-01, h_new = 4.283760915587160E-03, n_yes = 63, n_no = 0, h*lambda = 3.300774630149431E+00 +step(A) = 426, err = 6.964951299115636E-03, h_new = 4.190371271713398E-03, n_yes = 63, n_no = 1, h*lambda = 1.599612021625538E+00 +DoPri5: Dormand-Prince method (explicit, order 5(4), embedded) +Number of function evaluations = 2557 +Number of performed steps = 426 +Number of accepted steps = 406 +Number of rejected steps = 20 +y = 1.820788982019600E+00 -7.853646714269610E-01 +h = 4.190371271713398E-03 diff --git a/russell_ode/data/russell_dopri8_van_der_pol.txt b/russell_ode/data/russell_dopri8_van_der_pol.txt new file mode 100644 index 00000000..5b43d011 --- /dev/null +++ b/russell_ode/data/russell_dopri8_van_der_pol.txt @@ -0,0 +1,29 @@ + +running 1 test +step = 0, x = 0.00, y = 2.000000000000000E+00 0.000000000000000E+00 +step = 75, x = 0.10, y = 1.931611417503521E+00 -7.070951304422103E-01 +step = 128, x = 0.20, y = 1.858493744365171E+00 -7.571048744669554E-01 +step = 177, x = 0.30, y = 1.779738552663857E+00 -8.207783445798569E-01 +step = 243, x = 0.40, y = 1.693630125574211E+00 -9.059325720374017E-01 +step = 316, x = 0.50, y = 1.597323684590660E+00 -1.028599021504059E+00 +step = 389, x = 0.60, y = 1.485399833444222E+00 -1.229027713070121E+00 +step = 462, x = 0.70, y = 1.344513388516450E+00 -1.655964097159143E+00 +step = 525, x = 0.80, y = 1.102733648397954E+00 -4.482889105244459E+00 +step = 631, x = 0.90, y = -1.959570969572631E+00 6.898639986751883E-01 +step = 687, x = 1.00, y = -1.888370653004267E+00 7.357374919713421E-01 +step = 738, x = 1.10, y = -1.812037940682723E+00 7.932568826998678E-01 +step = 795, x = 1.20, y = -1.729133704161143E+00 8.685148671118843E-01 +step = 867, x = 1.30, y = -1.637364675331479E+00 9.733118754022781E-01 +step = 940, x = 1.40, y = -1.532633806061130E+00 1.134615914716497E+00 +step =1014, x = 1.50, y = -1.406045188743348E+00 1.434684142406632E+00 +step =1085, x = 1.60, y = -1.227570508061303E+00 2.379082948047412E+00 +step =1186, x = 1.70, y = 1.986867061111602E+00 -6.739232392526573E-01 +step =1246, x = 1.80, y = 1.917429871211335E+00 -7.162099959863539E-01 +step =1298, x = 1.90, y = 1.843292936179200E+00 -7.685214897380936E-01 +DoPri8: Dormand-Prince method (explicit, order 8(5,3), embedded) +Number of function evaluations = 21551 +Number of performed steps = 1469 +Number of accepted steps = 1348 +Number of rejected steps = 121 +y = 1.763234540172065E+00 -8.356886819302314E-01 +h = 8.652690947041405E-04 diff --git a/russell_ode/data/russell_dopri8_van_der_pol_debug.txt b/russell_ode/data/russell_dopri8_van_der_pol_debug.txt new file mode 100644 index 00000000..01051ad0 --- /dev/null +++ b/russell_ode/data/russell_dopri8_van_der_pol_debug.txt @@ -0,0 +1,247 @@ + +running 1 test +step(A) = 1, err = 1.959643608584751E-12, h_new = 6.000000000000001E-04, n_yes = 0, n_no = 1, h*lambda = 0.000000000000000E+00 +step(A) = 2, err = 2.955980241763151E-06, h_new = 2.651893439819611E-03, n_yes = 0, n_no = 2, h*lambda = 5.999638637630946E-01 +step(A) = 3, err = 3.289190837337582E-01, h_new = 2.742599827989497E-03, n_yes = 0, n_no = 3, h*lambda = 2.647684181863886E+00 +step(A) = 4, err = 2.963282903613057E-02, h_new = 3.832068947866670E-03, n_yes = 0, n_no = 4, h*lambda = 2.731697216878963E+00 +step(A) = 5, err = 1.765232930143058E-02, h_new = 5.712491558625865E-03, n_yes = 0, n_no = 5, h*lambda = 3.803790881742330E+00 +step(A) = 6, err = 1.357256912072538E-08, h_new = 3.427494935175519E-02, n_yes = 0, n_no = 6, h*lambda = 5.641296506168626E+00 +step(R) = 7, err = 3.049983717411964E+02, h_new = 1.508975334921279E-02 +step(R) = 8, err = 2.753687260554259E+01, h_new = 8.972951566428231E-03 +step(A) = 9, err = 1.190643628944207E-01, h_new = 1.053671198297923E-02, n_yes = 1, n_no = 0, h*lambda = 8.789351847635404E+00 +step(R) = 10, err = 3.891356958831525E+00, h_new = 6.814205067198184E-03 +step(A) = 11, err = 1.254148967995318E-01, h_new = 7.949943971702209E-03, n_yes = 2, n_no = 0, h*lambda = 6.633281242745822E+00 +step(A) = 12, err = 1.807919708478315E-01, h_new = 7.594694627827802E-03, n_yes = 3, n_no = 0, h*lambda = 6.591787685229763E+00 +step(R) = 13, err = 1.084024670823181E+00, h_new = 6.766637789798637E-03 +step(A) = 14, err = 2.161217864462167E-01, h_new = 7.375273340821484E-03, n_yes = 4, n_no = 0, h*lambda = 6.504692841485452E+00 +step(A) = 15, err = 2.488521128117724E-01, h_new = 7.246407831926661E-03, n_yes = 5, n_no = 0, h*lambda = 6.463674141766018E+00 +step(A) = 16, err = 7.435797916009501E-01, h_new = 6.767828343041878E-03, n_yes = 6, n_no = 0, h*lambda = 6.874624502067225E+00 +step(A) = 17, err = 5.600360754301060E-01, h_new = 6.548846995387800E-03, n_yes = 7, n_no = 0, h*lambda = 6.379539610061569E+00 +step(A) = 18, err = 2.689360418370497E-01, h_new = 6.945463402120771E-03, n_yes = 8, n_no = 0, h*lambda = 6.134291489598144E+00 +step(A) = 19, err = 4.737739680080070E-01, h_new = 6.862736709033492E-03, n_yes = 9, n_no = 0, h*lambda = 6.462359073026083E+00 +step(A) = 20, err = 4.081931746269030E-01, h_new = 6.908466078406463E-03, n_yes = 10, n_no = 0, h*lambda = 6.342614492411329E+00 +step(A) = 21, err = 3.936599455473775E-01, h_new = 6.986086930314580E-03, n_yes = 11, n_no = 0, h*lambda = 6.341752995120107E+00 +step(A) = 22, err = 4.078712125926050E-01, h_new = 7.033331916581695E-03, n_yes = 12, n_no = 0, h*lambda = 6.368533581553475E+00 +step(A) = 23, err = 4.114534444966370E-01, h_new = 7.073160854852484E-03, n_yes = 13, n_no = 0, h*lambda = 6.366738680575573E+00 +step(A) = 24, err = 4.033631445552880E-01, h_new = 7.130894602082410E-03, n_yes = 14, n_no = 0, h*lambda = 6.357033171755682E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 1.141460201163893E-01, ACCEPTED STEP = 21 +step(A) = 25, err = 4.077460796174435E-01, h_new = 7.179394245365928E-03, n_yes = 15, n_no = 0, h*lambda = 6.362627602591399E+00 +step(A) = 26, err = 4.037854317807910E-01, h_new = 7.237048469708767E-03, n_yes = 16, n_no = 0, h*lambda = 6.358575193129605E+00 +step(A) = 27, err = 4.074225933170899E-01, h_new = 7.286992996659531E-03, n_yes = 17, n_no = 0, h*lambda = 6.361764246182374E+00 +step(A) = 28, err = 4.025036245124692E-01, h_new = 7.348431279629915E-03, n_yes = 18, n_no = 0, h*lambda = 6.356714697340982E+00 +step(A) = 29, err = 4.066531571625553E-01, h_new = 7.400893045590363E-03, n_yes = 19, n_no = 0, h*lambda = 6.360744201258377E+00 +step(A) = 30, err = 4.015562149952802E-01, h_new = 7.465490436641724E-03, n_yes = 20, n_no = 0, h*lambda = 6.355444820504961E+00 +step(A) = 31, err = 4.058232561168469E-01, h_new = 7.520708164277470E-03, n_yes = 21, n_no = 0, h*lambda = 6.359535584937710E+00 +step(A) = 32, err = 4.004792806833026E-01, h_new = 7.588898415536484E-03, n_yes = 22, n_no = 0, h*lambda = 6.353975475294596E+00 +step(A) = 33, err = 4.049042412886897E-01, h_new = 7.647195765692192E-03, n_yes = 23, n_no = 0, h*lambda = 6.358248130003780E+00 +step(A) = 34, err = 3.993867276493583E-01, h_new = 7.719168373250756E-03, n_yes = 24, n_no = 0, h*lambda = 6.352456077091252E+00 +step(A) = 35, err = 4.038828130903215E-01, h_new = 7.780922719356324E-03, n_yes = 25, n_no = 0, h*lambda = 6.356790279244500E+00 +step(A) = 36, err = 3.981946527649910E-01, h_new = 7.857089195218053E-03, n_yes = 26, n_no = 0, h*lambda = 6.350804544563693E+00 +step(A) = 37, err = 4.027908749273922E-01, h_new = 7.922627551499691E-03, n_yes = 27, n_no = 0, h*lambda = 6.355226958190373E+00 +step(A) = 38, err = 3.968989619377170E-01, h_new = 8.003441114656762E-03, n_yes = 28, n_no = 0, h*lambda = 6.349002720017052E+00 +step(A) = 39, err = 4.016091858498869E-01, h_new = 8.073164627703900E-03, n_yes = 29, n_no = 0, h*lambda = 6.353526548462183E+00 +step(A) = 40, err = 3.954854477614776E-01, h_new = 8.159151639330675E-03, n_yes = 30, n_no = 0, h*lambda = 6.347032595111513E+00 +step(A) = 41, err = 4.003287180796385E-01, h_new = 8.233517650499239E-03, n_yes = 31, n_no = 0, h*lambda = 6.351673639178013E+00 +step(A) = 42, err = 3.939366753191192E-01, h_new = 8.325294943607174E-03, n_yes = 32, n_no = 0, h*lambda = 6.344867851462248E+00 +step(A) = 43, err = 3.989362259345737E-01, h_new = 8.404835222298360E-03, n_yes = 33, n_no = 0, h*lambda = 6.349646213130749E+00 +step(A) = 44, err = 3.922322408643247E-01, h_new = 8.503129665896961E-03, n_yes = 34, n_no = 0, h*lambda = 6.342478281687269E+00 +step(A) = 45, err = 3.974165319419509E-01, h_new = 8.588465393980182E-03, n_yes = 35, n_no = 0, h*lambda = 6.347418360364619E+00 +step(A) = 46, err = 3.903471745193924E-01, h_new = 8.694141405708858E-03, n_yes = 36, n_no = 0, h*lambda = 6.339826580525615E+00 +step(A) = 47, err = 3.957515764351033E-01, h_new = 8.786003607285610E-03, n_yes = 37, n_no = 0, h*lambda = 6.344958758630874E+00 +step(A) = 48, err = 3.882509034227840E-01, h_new = 8.900098790417888E-03, n_yes = 38, n_no = 0, h*lambda = 6.336866791919356E+00 +step(A) = 49, err = 3.939197894170920E-01, h_new = 8.999354543601162E-03, n_yes = 39, n_no = 0, h*lambda = 6.342229201053338E+00 +step(A) = 50, err = 3.859055069870121E-01, h_new = 9.123127603110105E-03, n_yes = 40, n_no = 0, h*lambda = 6.333541511274128E+00 +step(A) = 51, err = 3.918952252785496E-01, h_new = 9.230814257894130E-03, n_yes = 41, n_no = 0, h*lambda = 6.339182626315827E+00 +step(A) = 52, err = 3.832633846440414E-01, h_new = 9.365810261289602E-03, n_yes = 42, n_no = 0, h*lambda = 6.329778111193712E+00 +step(A) = 53, err = 3.896464417281836E-01, h_new = 9.483180686924636E-03, n_yes = 43, n_no = 0, h*lambda = 6.335760400844509E+00 +step(A) = 54, err = 3.802639546766667E-01, h_new = 9.631321714330935E-03, n_yes = 44, n_no = 0, h*lambda = 6.325483341361224E+00 +step(A) = 55, err = 3.871350480801914E-01, h_new = 9.759904944430943E-03, n_yes = 45, n_no = 0, h*lambda = 6.331888556172611E+00 +step(A) = 56, err = 3.768289131763770E-01, h_new = 9.923618730297691E-03, n_yes = 46, n_no = 0, h*lambda = 6.320535467638130E+00 +step(A) = 57, err = 3.843138179502145E-01, h_new = 1.006530245402551E-02, n_yes = 47, n_no = 0, h*lambda = 6.327472463893940E+00 +step(A) = 58, err = 3.728552563016018E-01, h_new = 1.024770949276509E-02, n_yes = 48, n_no = 0, h*lambda = 6.314772533688188E+00 +step(A) = 59, err = 3.811242574925869E-01, h_new = 1.040485400558744E-02, n_yes = 49, n_no = 0, h*lambda = 6.322389159120288E+00 +step(A) = 60, err = 3.682047478416105E-01, h_new = 1.061004747695191E-02, n_yes = 50, n_no = 0, h*lambda = 6.307974325811323E+00 +step(A) = 61, err = 3.774935232491197E-01, h_new = 1.078564566634672E-02, n_yes = 51, n_no = 0, h*lambda = 6.316476004545933E+00 +step(A) = 62, err = 3.626875322858370E-01, h_new = 1.101912423945174E-02, n_yes = 52, n_no = 0, h*lambda = 6.299833750983720E+00 +step(A) = 63, err = 3.733307277582207E-01, h_new = 1.121702975837375E-02, n_yes = 53, n_no = 0, h*lambda = 6.309513556908893E+00 +step(A) = 64, err = 3.560357714675643E-01, h_new = 1.148639315495112E-02, n_yes = 54, n_no = 0, h*lambda = 6.289909649359084E+00 +step(A) = 65, err = 3.685230975757703E-01, h_new = 1.171165033412299E-02, n_yes = 55, n_no = 0, h*lambda = 6.301198914583646E+00 +step(A) = 66, err = 3.478595230827699E-01, h_new = 1.202777013976067E-02, n_yes = 56, n_no = 0, h*lambda = 6.277545458299144E+00 +step(A) = 67, err = 3.629338983026488E-01, h_new = 1.228709419159808E-02, n_yes = 57, n_no = 0, h*lambda = 6.291103027687949E+00 +step(A) = 68, err = 3.375694804624528E-01, h_new = 1.266619879806891E-02, n_yes = 58, n_no = 0, h*lambda = 6.261721378695177E+00 +step(A) = 69, err = 3.564085409656642E-01, h_new = 1.296866573819446E-02, n_yes = 59, n_no = 0, h*lambda = 6.278600116188233E+00 +step(A) = 70, err = 3.242343503393830E-01, h_new = 1.343632277485641E-02, n_yes = 60, n_no = 0, h*lambda = 6.240767852310240E+00 +step(A) = 71, err = 3.488100811346173E-01, h_new = 1.379428863639130E-02, n_yes = 61, n_no = 0, h*lambda = 6.262747484378289E+00 +step(A) = 72, err = 3.063010455081115E-01, h_new = 1.439372690955129E-02, n_yes = 62, n_no = 0, h*lambda = 6.211764538525046E+00 +step(A) = 73, err = 3.401584205353467E-01, h_new = 1.482366578389972E-02, n_yes = 63, n_no = 0, h*lambda = 6.242077724942510E+00 +step(A) = 74, err = 2.810069967333150E-01, h_new = 1.563538150446634E-02, n_yes = 64, n_no = 0, h*lambda = 6.169147305053520E+00 +step(A) = 75, err = 3.311667960225096E-01, h_new = 1.615642034103204E-02, n_yes = 65, n_no = 0, h*lambda = 6.214256864008449E+00 +step(A) = 76, err = 2.430597376919716E-01, h_new = 1.735295837376587E-02, n_yes = 66, n_no = 0, h*lambda = 6.101034621382365E+00 +step(A) = 77, err = 3.256047954130120E-01, h_new = 1.796923901338519E-02, n_yes = 67, n_no = 0, h*lambda = 6.175753177141573E+00 +step(A) = 78, err = 1.815900522247110E-01, h_new = 2.001638695314420E-02, n_yes = 67, n_no = 1, h*lambda = 5.977717665384551E+00 +step(A) = 79, err = 3.447500895105198E-01, h_new = 2.057975257841976E-02, n_yes = 68, n_no = 0, h*lambda = 6.124456071313491E+00 +step(A) = 80, err = 7.494034675413233E-02, h_new = 2.560608579408087E-02, n_yes = 68, n_no = 1, h*lambda = 5.705940812265488E+00 +step(A) = 81, err = 6.648330042200872E-01, h_new = 2.425194833393564E-02, n_yes = 69, n_no = 0, h*lambda = 6.133098554838670E+00 +step(A) = 82, err = 5.871646265587989E-02, h_new = 3.110958074663097E-02, n_yes = 69, n_no = 1, h*lambda = 4.867384687888820E+00 +step(A) = 83, err = 1.224305390121934E-01, h_new = 3.640411247942137E-02, n_yes = 69, n_no = 2, h*lambda = 4.522823325693174E+00 +step(A) = 84, err = 7.040558090852383E-02, h_new = 4.565013552831390E-02, n_yes = 70, n_no = 0, h*lambda = 7.528228594189563E+00 +step(R) = 85, err = 3.615022305922024E+02, h_new = 1.967527032528365E-02 +step(A) = 86, err = 1.763775523022788E-01, h_new = 2.199671376398370E-02, n_yes = 70, n_no = 1, h*lambda = 1.307303773393392E+00 +step(R) = 87, err = 1.058639432983636E+03, h_new = 7.414292393516595E-03 +step(A) = 88, err = 8.193024911412709E-03, h_new = 1.216554820969884E-02, n_yes = 70, n_no = 2, h*lambda = 1.075904239144414E+00 +step(A) = 89, err = 3.247215844295357E-01, h_new = 7.680214044927170E-03, n_yes = 70, n_no = 3, h*lambda = 2.517281179893591E+00 +step(R) = 90, err = 4.696476186855632E+02, h_new = 3.203644500959392E-03 +step(A) = 91, err = 5.081123230352770E-01, h_new = 3.137919956173768E-03, n_yes = 70, n_no = 4, h*lambda = 9.330803614006128E-01 +step(R) = 92, err = 1.109363580787332E+01, h_new = 2.090499810696303E-03 +step(A) = 93, err = 2.131026583381040E-02, h_new = 3.043820471380126E-03, n_yes = 70, n_no = 5, h*lambda = 2.978929434649977E+00 +step(R) = 94, err = 1.008394294728542E+00, h_new = 1.879484915680574E-03 +step(A) = 95, err = 5.003903492924712E-01, h_new = 1.844453616736991E-03, n_yes = 0, n_no = 6, h*lambda = 1.780820385048990E+00 +step(A) = 96, err = 3.285995448564784E-03, h_new = 3.392554850781245E-03, n_yes = 0, n_no = 7, h*lambda = 1.841306959171333E+00 +step(R) = 97, err = 8.454092412084753E+00, h_new = 2.338223460305420E-03 +step(A) = 98, err = 4.898107722894796E-01, h_new = 2.300779406806035E-03, n_yes = 0, n_no = 8, h*lambda = 2.364459953169361E+00 +step(A) = 99, err = 1.400696007694514E-01, h_new = 2.647430114463808E-03, n_yes = 0, n_no = 9, h*lambda = 2.323978779787861E+00 +step(A) = 100, err = 4.137503865818436E-02, h_new = 3.547927302598528E-03, n_yes = 0, n_no = 10, h*lambda = 2.668153874405383E+00 +step(A) = 101, err = 2.165847589572482E-02, h_new = 5.155416241953965E-03, n_yes = 0, n_no = 11, h*lambda = 3.564561765352872E+00 +step(A) = 102, err = 2.127984143923389E-03, h_new = 1.001175460994385E-02, n_yes = 0, n_no = 12, h*lambda = 5.156008611184090E+00 +step(A) = 103, err = 4.021520106838782E-01, h_new = 1.009726897475024E-02, n_yes = 1, n_no = 0, h*lambda = 9.923844895667671E+00 +step(R) = 104, err = 5.101997711014393E+01, h_new = 5.558772525402288E-03 +step(A) = 105, err = 1.004589597113831E-02, h_new = 8.891454843187187E-03, n_yes = 1, n_no = 1, h*lambda = 5.482430601629035E+00 +step(A) = 106, err = 2.275060087048925E-03, h_new = 1.070526205365635E-02, n_yes = 1, n_no = 2, h*lambda = 5.454889821839830E+00 +step(R) = 107, err = 2.885979263985814E+00, h_new = 8.439236574258852E-03 +step(A) = 108, err = 2.070056635047224E-01, h_new = 9.248001520487967E-03, n_yes = 2, n_no = 0, h*lambda = 8.217908765775206E+00 +step(R) = 109, err = 3.266643304374915E+00, h_new = 6.550628291507218E-03 +step(A) = 110, err = 1.083510160454219E-01, h_new = 7.783435708271628E-03, n_yes = 3, n_no = 0, h*lambda = 6.340484855092033E+00 +step(A) = 111, err = 9.254398538587791E-02, h_new = 7.938381042042702E-03, n_yes = 4, n_no = 0, h*lambda = 6.302012023103454E+00 +step(R) = 112, err = 1.160859077868534E+00, h_new = 7.012566826296985E-03 +step(A) = 113, err = 2.256803469098227E-01, h_new = 7.602086462328807E-03, n_yes = 5, n_no = 0, h*lambda = 6.702343543880890E+00 +step(A) = 114, err = 3.633184744492227E-01, h_new = 7.162812570160331E-03, n_yes = 6, n_no = 0, h*lambda = 6.658065314799454E+00 +step(A) = 115, err = 7.447589687445751E-01, h_new = 6.688429116287333E-03, n_yes = 7, n_no = 0, h*lambda = 6.754678667850584E+00 +step(A) = 116, err = 4.270349974625846E-01, h_new = 6.695125227983594E-03, n_yes = 8, n_no = 0, h*lambda = 6.266807689346519E+00 +step(A) = 117, err = 3.241872066409964E-01, h_new = 6.936681114007103E-03, n_yes = 9, n_no = 0, h*lambda = 6.232733353761762E+00 +step(A) = 118, err = 4.312213402952816E-01, h_new = 6.935163573105066E-03, n_yes = 10, n_no = 0, h*lambda = 6.413926119398242E+00 +step(A) = 119, err = 4.187789015310238E-01, h_new = 6.959068623234852E-03, n_yes = 11, n_no = 0, h*lambda = 6.369070232345154E+00 +step(A) = 120, err = 3.975480455722806E-01, h_new = 7.028617762077525E-03, n_yes = 12, n_no = 0, h*lambda = 6.346897022385348E+00 +step(A) = 121, err = 4.084659136816667E-01, h_new = 7.074861747007168E-03, n_yes = 13, n_no = 0, h*lambda = 6.365529556739990E+00 +step(A) = 122, err = 4.060100736113399E-01, h_new = 7.126780217979930E-03, n_yes = 14, n_no = 0, h*lambda = 6.361635455885664E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 9.881349085403395E-01, ACCEPTED STEP = 109 +step(A) = 123, err = 4.079019963179500E-01, h_new = 7.174908986524097E-03, n_yes = 15, n_no = 0, h*lambda = 6.362085653171753E+00 +step(A) = 124, err = 4.032567986134078E-01, h_new = 7.233711661354258E-03, n_yes = 16, n_no = 0, h*lambda = 6.357788804634044E+00 +step(A) = 125, err = 4.075247140424253E-01, h_new = 7.283404986501235E-03, n_yes = 17, n_no = 0, h*lambda = 6.362071764322403E+00 +step(A) = 126, err = 4.026373249686707E-01, h_new = 7.344508107412362E-03, n_yes = 18, n_no = 0, h*lambda = 6.356878640221078E+00 +step(A) = 127, err = 4.066441324897730E-01, h_new = 7.396962385030836E-03, n_yes = 19, n_no = 0, h*lambda = 6.360703437561154E+00 +step(A) = 128, err = 4.015773440469292E-01, h_new = 7.461476393215391E-03, n_yes = 20, n_no = 0, h*lambda = 6.355482753849844E+00 +step(A) = 129, err = 4.058625590945953E-01, h_new = 7.516573440058212E-03, n_yes = 21, n_no = 0, h*lambda = 6.359594648580982E+00 +step(A) = 130, err = 4.005156655050936E-01, h_new = 7.584640069104633E-03, n_yes = 22, n_no = 0, h*lambda = 6.354023251522405E+00 +step(A) = 131, err = 4.049351178695631E-01, h_new = 7.642831857439511E-03, n_yes = 23, n_no = 0, h*lambda = 6.358291792599794E+00 +step(A) = 132, err = 3.994252920466129E-01, h_new = 7.714670282283922E-03, n_yes = 24, n_no = 0, h*lambda = 6.352510089511902E+00 +step(A) = 133, err = 4.039185190610339E-01, h_new = 7.776302711687718E-03, n_yes = 25, n_no = 0, h*lambda = 6.356841224056922E+00 +step(A) = 134, err = 3.982362203177404E-01, h_new = 7.852321504541368E-03, n_yes = 26, n_no = 0, h*lambda = 6.350862150821630E+00 +step(A) = 135, err = 4.028289395935568E-01, h_new = 7.917726565514023E-03, n_yes = 27, n_no = 0, h*lambda = 6.355281614192799E+00 +step(A) = 136, err = 3.969443648511441E-01, h_new = 7.998375771719217E-03, n_yes = 28, n_no = 0, h*lambda = 6.349065935258063E+00 +step(A) = 137, err = 4.016504421050673E-01, h_new = 8.067951561785157E-03, n_yes = 29, n_no = 0, h*lambda = 6.353586059958412E+00 +step(A) = 138, err = 3.955350425579007E-01, h_new = 8.153755243741836E-03, n_yes = 30, n_no = 0, h*lambda = 6.347101808573513E+00 +step(A) = 139, err = 4.003734968990105E-01, h_new = 8.227957033076687E-03, n_yes = 31, n_no = 0, h*lambda = 6.351738620995963E+00 +step(A) = 140, err = 3.939911327552290E-01, h_new = 8.319528591365552E-03, n_yes = 32, n_no = 0, h*lambda = 6.344944073731781E+00 +step(A) = 141, err = 3.989850009816002E-01, h_new = 8.398885426036295E-03, n_yes = 33, n_no = 0, h*lambda = 6.349717450914558E+00 +step(A) = 142, err = 3.922923068904342E-01, h_new = 8.496947646052787E-03, n_yes = 34, n_no = 0, h*lambda = 6.342562622408228E+00 +step(A) = 143, err = 3.974698584095905E-01, h_new = 8.582077395147497E-03, n_yes = 35, n_no = 0, h*lambda = 6.347496810339476E+00 +step(A) = 144, err = 3.904137727538592E-01, h_new = 8.687489545495846E-03, n_yes = 36, n_no = 0, h*lambda = 6.339920422676033E+00 +step(A) = 145, err = 3.958101132610834E-01, h_new = 8.779119155799006E-03, n_yes = 37, n_no = 0, h*lambda = 6.345045574478969E+00 +step(A) = 146, err = 3.883251696166194E-01, h_new = 8.892912321123846E-03, n_yes = 38, n_no = 0, h*lambda = 6.336971848569470E+00 +step(A) = 147, err = 3.939843275795699E-01, h_new = 8.991903793039649E-03, n_yes = 39, n_no = 0, h*lambda = 6.342325798402557E+00 +step(A) = 148, err = 3.859888574031421E-01, h_new = 9.115328302951474E-03, n_yes = 40, n_no = 0, h*lambda = 6.333659933819797E+00 +step(A) = 149, err = 3.919667192995380E-01, h_new = 9.222712599801779E-03, n_yes = 41, n_no = 0, h*lambda = 6.339290757191063E+00 +step(A) = 150, err = 3.833576078409316E-01, h_new = 9.357302596279624E-03, n_yes = 42, n_no = 0, h*lambda = 6.329912639166085E+00 +step(A) = 151, err = 3.897260516429165E-01, h_new = 9.474324461012566E-03, n_yes = 43, n_no = 0, h*lambda = 6.335882260439314E+00 +step(A) = 152, err = 3.803713436813264E-01, h_new = 9.621987519340352E-03, n_yes = 44, n_no = 0, h*lambda = 6.325637519742799E+00 +step(A) = 153, err = 3.872241957500186E-01, h_new = 9.750165508397418E-03, n_yes = 45, n_no = 0, h*lambda = 6.332026930544010E+00 +step(A) = 154, err = 3.769524535331337E-01, h_new = 9.913309732131777E-03, n_yes = 46, n_no = 0, h*lambda = 6.320713961258272E+00 +step(A) = 155, err = 3.844142585651864E-01, h_new = 1.005451783828186E-02, n_yes = 47, n_no = 0, h*lambda = 6.327630943070814E+00 +step(A) = 156, err = 3.729989108608491E-01, h_new = 1.023623653721280E-02, n_yes = 48, n_no = 0, h*lambda = 6.314981608331261E+00 +step(A) = 157, err = 3.812381707735522E-01, h_new = 1.039281688223251E-02, n_yes = 49, n_no = 0, h*lambda = 6.322572443113506E+00 +step(A) = 158, err = 3.683738873243810E-01, h_new = 1.059716459989047E-02, n_yes = 50, n_no = 0, h*lambda = 6.308222594051161E+00 +step(A) = 159, err = 3.776236286999907E-01, h_new = 1.077208556217713E-02, n_yes = 51, n_no = 0, h*lambda = 6.316690366353345E+00 +step(A) = 160, err = 3.628896198803264E-01, h_new = 1.100450432781832E-02, n_yes = 52, n_no = 0, h*lambda = 6.300133377594372E+00 +step(A) = 161, err = 3.734804154047202E-01, h_new = 1.120158595678836E-02, n_yes = 53, n_no = 0, h*lambda = 6.309767543152950E+00 +step(A) = 162, err = 3.562814773028236E-01, h_new = 1.146958936996061E-02, n_yes = 54, n_no = 0, h*lambda = 6.290278371606470E+00 +step(A) = 163, err = 3.686965462407190E-01, h_new = 1.169382917979576E-02, n_yes = 55, n_no = 0, h*lambda = 6.301504456379979E+00 +step(A) = 164, err = 3.481646291295528E-01, h_new = 1.200815192625844E-02, n_yes = 56, n_no = 0, h*lambda = 6.278010148939095E+00 +step(A) = 165, err = 3.631360706568778E-01, h_new = 1.226619909791885E-02, n_yes = 57, n_no = 0, h*lambda = 6.291477240342902E+00 +step(A) = 166, err = 3.379582856655150E-01, h_new = 1.264283970754352E-02, n_yes = 58, n_no = 0, h*lambda = 6.262324685746775E+00 +step(A) = 167, err = 3.566446940923966E-01, h_new = 1.294367710105359E-02, n_yes = 59, n_no = 0, h*lambda = 6.279068319252324E+00 +step(A) = 168, err = 3.247461179642027E-01, h_new = 1.340778952214271E-02, n_yes = 60, n_no = 0, h*lambda = 6.241581476393391E+00 +step(A) = 169, err = 3.490835840132996E-01, h_new = 1.376364665935072E-02, n_yes = 61, n_no = 0, h*lambda = 6.263348396677960E+00 +step(A) = 170, err = 3.070030097859534E-01, h_new = 1.435764447702505E-02, n_yes = 62, n_no = 0, h*lambda = 6.212918157986235E+00 +step(A) = 171, err = 3.404624901800023E-01, h_new = 1.478485418553244E-02, n_yes = 63, n_no = 0, h*lambda = 6.242872417722190E+00 +step(A) = 172, err = 2.820225028211756E-01, h_new = 1.558741451863407E-02, n_yes = 64, n_no = 0, h*lambda = 6.170898882029289E+00 +step(A) = 173, err = 3.314517489671439E-01, h_new = 1.610512332902568E-02, n_yes = 65, n_no = 0, h*lambda = 6.215342444317806E+00 +step(A) = 174, err = 2.446329688669694E-01, h_new = 1.728391775565643E-02, n_yes = 66, n_no = 0, h*lambda = 6.103967406447158E+00 +step(A) = 175, err = 3.256218238315504E-01, h_new = 1.789762945804292E-02, n_yes = 67, n_no = 0, h*lambda = 6.177263853041123E+00 +step(A) = 176, err = 1.842322069951937E-01, h_new = 1.990065301900324E-02, n_yes = 67, n_no = 1, h*lambda = 5.983408374577461E+00 +step(A) = 177, err = 3.429173868058848E-01, h_new = 2.047439835655097E-02, n_yes = 68, n_no = 0, h*lambda = 6.126272397296150E+00 +step(A) = 178, err = 7.954959697476374E-02, h_new = 2.528563766129668E-02, n_yes = 68, n_no = 1, h*lambda = 5.719907971052866E+00 +step(A) = 179, err = 6.268881034585364E-01, h_new = 2.412501911893139E-02, n_yes = 69, n_no = 0, h*lambda = 6.125818984819251E+00 +step(A) = 180, err = 5.092875184405649E-02, h_new = 3.150212109047433E-02, n_yes = 69, n_no = 1, h*lambda = 4.919484062565088E+00 +step(A) = 181, err = 1.868841551177909E-01, h_new = 3.496517818686758E-02, n_yes = 69, n_no = 2, h*lambda = 4.669241493677491E+00 +step(A) = 182, err = 3.326366677391521E-02, h_new = 4.815395804848805E-02, n_yes = 69, n_no = 3, h*lambda = 1.170797699392677E+00 +step(R) = 183, err = 3.842551409515108E+02, h_new = 2.059667132305313E-02 +step(A) = 184, err = 1.290014854040249E-01, h_new = 2.394502028185396E-02, n_yes = 69, n_no = 4, h*lambda = 1.037811943791500E+00 +step(R) = 185, err = 1.637438205407510E+03, h_new = 7.349688505455062E-03 +step(A) = 186, err = 3.387749438943271E-03, h_new = 1.346705185528980E-02, n_yes = 69, n_no = 5, h*lambda = 8.051119718026939E-01 +step(A) = 187, err = 2.392761236079284E-02, h_new = 1.054748756655955E-02, n_yes = 0, n_no = 6, h*lambda = 2.017037638743173E+00 +step(R) = 188, err = 5.883488095930951E+02, h_new = 4.277471963508097E-03 +step(A) = 189, err = 1.063374573624309E-02, h_new = 6.793504964975481E-03, n_yes = 0, n_no = 7, h*lambda = 1.268282415789568E+00 +step(R) = 190, err = 1.442587436609572E+02, h_new = 2.067935138765545E-03 +step(A) = 191, err = 5.499805441072779E-02, h_new = 2.674459382315194E-03, n_yes = 0, n_no = 8, h*lambda = 2.355470058554151E-01 +step(A) = 192, err = 1.744534350102161E-01, h_new = 2.315098543205909E-03, n_yes = 0, n_no = 9, h*lambda = 1.926759985625812E+00 +step(A) = 193, err = 3.666618956778373E-01, h_new = 2.361993883728983E-03, n_yes = 0, n_no = 10, h*lambda = 2.322100850840346E+00 +step(A) = 194, err = 4.677865828830713E-01, h_new = 2.337573676371686E-03, n_yes = 0, n_no = 11, h*lambda = 2.388613437746681E+00 +step(A) = 195, err = 2.521765574895837E-01, h_new = 2.499163839700966E-03, n_yes = 0, n_no = 12, h*lambda = 2.362449723593110E+00 +step(A) = 196, err = 3.993143516754681E-02, h_new = 3.364130798558515E-03, n_yes = 0, n_no = 13, h*lambda = 2.520559670852673E+00 +step(A) = 197, err = 2.745213568075585E-02, h_new = 4.745624432900489E-03, n_yes = 0, n_no = 14, h*lambda = 3.382937635482347E+00 +step(A) = 198, err = 4.695448262158042E-03, h_new = 8.347880861623733E-03, n_yes = 0, n_no = 15, h*lambda = 4.752200023042021E+00 +step(A) = 199, err = 3.874626559229092E-02, h_new = 1.127950446190545E-02, n_yes = 1, n_no = 0, h*lambda = 8.297507000214861E+00 +step(R) = 200, err = 1.788279985210553E+01, h_new = 7.079100702160043E-03 +step(A) = 201, err = 8.780765044813300E-02, h_new = 8.635332314107911E-03, n_yes = 2, n_no = 0, h*lambda = 6.991775536914764E+00 +step(A) = 202, err = 2.295900213251939E-01, h_new = 7.657755108980075E-03, n_yes = 3, n_no = 0, h*lambda = 6.947147281357447E+00 +step(R) = 203, err = 1.540351989605349E+00, h_new = 6.529673807010395E-03 +step(A) = 204, err = 1.623910501047941E-01, h_new = 7.375878168033652E-03, n_yes = 4, n_no = 0, h*lambda = 6.369850773666425E+00 +step(A) = 205, err = 1.471673480326652E-01, h_new = 7.467196205918202E-03, n_yes = 5, n_no = 0, h*lambda = 6.331783147923894E+00 +step(A) = 206, err = 9.090929465628425E-01, h_new = 6.801019754116433E-03, n_yes = 6, n_no = 0, h*lambda = 7.190828849618154E+00 +step(A) = 207, err = 8.586420906901946E-01, h_new = 6.238641415878820E-03, n_yes = 7, n_no = 0, h*lambda = 6.508043126677835E+00 +step(A) = 208, err = 1.522371922694471E-01, h_new = 7.104236942839067E-03, n_yes = 7, n_no = 1, h*lambda = 5.934757537299263E+00 +step(A) = 209, err = 8.063558534419009E-01, h_new = 6.568165757497788E-03, n_yes = 8, n_no = 0, h*lambda = 6.712943443676473E+00 +step(A) = 210, err = 3.428984929415700E-01, h_new = 6.757575657121319E-03, n_yes = 9, n_no = 0, h*lambda = 6.167339484362073E+00 +step(A) = 211, err = 3.716930356153604E-01, h_new = 6.882724295653092E-03, n_yes = 10, n_no = 0, h*lambda = 6.304103623839228E+00 +step(A) = 212, err = 4.108509636606573E-01, h_new = 6.922968313882586E-03, n_yes = 11, n_no = 0, h*lambda = 6.377871370766266E+00 +step(A) = 213, err = 4.156416751314628E-01, h_new = 6.953364043900352E-03, n_yes = 12, n_no = 0, h*lambda = 6.371886370687702E+00 +step(A) = 214, err = 4.032734016940134E-01, h_new = 7.010314946932111E-03, n_yes = 13, n_no = 0, h*lambda = 6.355833963368388E+00 +step(A) = 215, err = 4.080230234532179E-01, h_new = 7.057395486163307E-03, n_yes = 14, n_no = 0, h*lambda = 6.363352471292647E+00 +THE PROBLEM SEEMS TO BECOME STIFF AT X = 1.853542600301926E+00, ACCEPTED STEP = 196 +step(A) = 216, err = 4.051405358750267E-01, h_new = 7.111091265374089E-03, n_yes = 15, n_no = 0, h*lambda = 6.360565934524319E+00 +step(A) = 217, err = 4.083105016384038E-01, h_new = 7.158218374475810E-03, n_yes = 16, n_no = 0, h*lambda = 6.362957568498087E+00 +step(A) = 218, err = 4.035562970806665E-01, h_new = 7.216214542809917E-03, n_yes = 17, n_no = 0, h*lambda = 6.358114379805242E+00 +step(A) = 219, err = 4.075497440466565E-01, h_new = 7.265731887459975E-03, n_yes = 18, n_no = 0, h*lambda = 6.362067250408330E+00 +step(A) = 220, err = 4.027779737382985E-01, h_new = 7.326366886554049E-03, n_yes = 19, n_no = 0, h*lambda = 6.357093995179628E+00 +step(A) = 221, err = 4.067895906014911E-01, h_new = 7.378361743268916E-03, n_yes = 20, n_no = 0, h*lambda = 6.360918581465901E+00 +step(A) = 222, err = 4.017445113258848E-01, h_new = 7.442326334591912E-03, n_yes = 21, n_no = 0, h*lambda = 6.355702207864065E+00 +step(A) = 223, err = 4.059972150232856E-01, h_new = 7.496971103183568E-03, n_yes = 22, n_no = 0, h*lambda = 6.359782047495956E+00 +step(A) = 224, err = 4.006831837874366E-01, h_new = 7.564464809014140E-03, n_yes = 23, n_no = 0, h*lambda = 6.354257828243836E+00 +step(A) = 225, err = 4.050928328971827E-01, h_new = 7.622130783957619E-03, n_yes = 24, n_no = 0, h*lambda = 6.358516102041843E+00 +step(A) = 226, err = 3.996066818045246E-01, h_new = 7.693337997876921E-03, n_yes = 25, n_no = 0, h*lambda = 6.352760684234911E+00 +step(A) = 227, err = 4.040853782332110E-01, h_new = 7.754399657359072E-03, n_yes = 26, n_no = 0, h*lambda = 6.357079519275209E+00 +step(A) = 228, err = 3.984334155051135E-01, h_new = 7.829719805842093E-03, n_yes = 27, n_no = 0, h*lambda = 6.351136028405311E+00 +step(A) = 229, err = 4.030094034708480E-01, h_new = 7.894494611882713E-03, n_yes = 28, n_no = 0, h*lambda = 6.355540435324115E+00 +step(A) = 230, err = 3.971588507664018E-01, h_new = 7.974368695611184E-03, n_yes = 29, n_no = 0, h*lambda = 6.349364393439246E+00 +step(A) = 231, err = 4.018455935329237E-01, h_new = 8.043247257784502E-03, n_yes = 30, n_no = 0, h*lambda = 6.353867464793423E+00 +step(A) = 232, err = 3.957694744807013E-01, h_new = 8.128186169813201E-03, n_yes = 31, n_no = 0, h*lambda = 6.347428900264593E+00 +step(A) = 233, err = 4.005852757825183E-01, h_new = 8.201613114029158E-03, n_yes = 32, n_no = 0, h*lambda = 6.352045756228135E+00 +step(A) = 234, err = 3.942483872473201E-01, h_new = 8.292214880792535E-03, n_yes = 33, n_no = 0, h*lambda = 6.345304038936755E+00 +step(A) = 235, err = 6.469296982213430E-05, h_new = 6.908421836846476E-03, n_yes = 33, n_no = 1, h*lambda = 1.773468275403546E+00 +DoPri8: Dormand-Prince method (explicit, order 8(5,3), embedded) +Number of function evaluations = 3015 +Number of performed steps = 235 +Number of accepted steps = 215 +Number of rejected steps = 20 +y = 1.819907446132135E+00 -7.866363473440124E-01 +h = 6.908421836846476E-03 diff --git a/russell_ode/data/russell_radau5_amplifier1t.txt b/russell_ode/data/russell_radau5_amplifier1t.txt new file mode 100644 index 00000000..892d507a --- /dev/null +++ b/russell_ode/data/russell_radau5_amplifier1t.txt @@ -0,0 +1,64 @@ + +running 1 test +step = 0, x = 0.0000, y1and5 = 0.000000000000000E+00 0.000000000000000E+00, diff1and5 = 0.0E+00 0.0E+00 +step = 12, x = 0.0010, y1and5 = 1.916311926796952E-01 -2.688742328314606E+00, diff1and5 = 4.1E-08 2.5E-05 +step = 16, x = 0.0020, y1and5 = 3.218545459857997E-01 -1.744730225557849E+00, diff1and5 = 8.7E-07 1.5E-04 +step = 17, x = 0.0030, y1and5 = 3.335987191469630E-01 -7.596643438825829E-01, diff1and5 = 2.7E-06 1.6E-04 +step = 20, x = 0.0040, y1and5 = 2.215636007078548E-01 -1.395458336432019E-01, diff1and5 = 3.1E-08 4.0E-06 +step = 23, x = 0.0050, y1and5 = 2.822200240466802E-02 5.035341147888064E-02, diff1and5 = 1.2E-06 9.4E-06 +step = 24, x = 0.0060, y1and5 = -1.725991658661986E-01 6.096730363234124E-02, diff1and5 = 4.3E-06 3.0E-04 +step = 27, x = 0.0070, y1and5 = -3.052894121289478E-01 -4.074905119116261E-01, diff1and5 = 2.7E-07 2.1E-04 +step = 30, x = 0.0080, y1and5 = -3.208689480273689E-01 -2.003438545924757E+00, diff1and5 = 5.2E-08 1.2E-04 +step = 33, x = 0.0090, y1and5 = -2.112947302771765E-01 -2.771120812467848E+00, diff1and5 = 7.6E-06 9.8E-05 +step = 35, x = 0.0100, y1and5 = -1.964755260832735E-02 -2.909025267207428E+00, diff1and5 = 1.2E-06 9.4E-06 +step = 36, x = 0.0110, y1and5 = 1.805509844329725E-01 -2.399268748293620E+00, diff1and5 = 5.9E-06 3.9E-04 +step = 38, x = 0.0120, y1and5 = 3.126188413107825E-01 -1.460587294362982E+00, diff1and5 = 4.7E-07 3.3E-04 +step = 39, x = 0.0130, y1and5 = 3.258822671433425E-01 -4.922170285774613E-01, diff1and5 = 5.6E-06 1.3E-04 +step = 43, x = 0.0140, y1and5 = 2.151031987252550E-01 1.085467149160739E-01, diff1and5 = 3.4E-07 1.6E-05 +step = 45, x = 0.0150, y1and5 = 2.281762592295544E-02 2.830230580502269E-01, diff1and5 = 1.7E-06 2.6E-05 +step = 46, x = 0.0160, y1and5 = -1.771028609942352E-01 2.901769168707256E-01, diff1and5 = 5.2E-06 3.0E-04 +step = 48, x = 0.0170, y1and5 = -3.089261062494018E-01 -1.143019737034166E-01, diff1and5 = 2.9E-07 1.5E-04 +step = 53, x = 0.0180, y1and5 = -3.239999209426208E-01 -1.773489735052747E+00, diff1and5 = 1.7E-07 1.5E-05 +step = 55, x = 0.0190, y1and5 = -2.139062023861129E-01 -2.549976140009503E+00, diff1and5 = 2.4E-07 4.3E-04 +step = 57, x = 0.0200, y1and5 = -2.183476092114947E-02 -2.694693795055462E+00, diff1and5 = 7.8E-07 1.6E-05 +step = 58, x = 0.0210, y1and5 = 1.787239963326930E-01 -2.190379451978742E+00, diff1and5 = 5.6E-06 8.5E-05 +step = 60, x = 0.0220, y1and5 = 3.110923612041102E-01 -1.257994974815820E+00, diff1and5 = 4.1E-07 3.1E-05 +step = 61, x = 0.0230, y1and5 = 3.246041884373977E-01 -2.951538111326443E-01, diff1and5 = 3.2E-06 3.3E-04 +step = 64, x = 0.0240, y1and5 = 2.140352114671024E-01 2.997035675328908E-01, diff1and5 = 7.0E-08 5.5E-06 +step = 66, x = 0.0250, y1and5 = 2.192275843691317E-02 4.688416135226632E-01, diff1and5 = 5.5E-08 6.9E-07 +step = 68, x = 0.0260, y1and5 = -1.778526757699608E-01 4.723850877530753E-01, diff1and5 = 3.3E-08 5.7E-06 +step = 71, x = 0.0270, y1and5 = -3.095275377534654E-01 7.592242763952231E-02, diff1and5 = 7.0E-08 3.2E-05 +step = 75, x = 0.0280, y1and5 = -3.245175839432364E-01 -1.595988841865317E+00, diff1and5 = 1.5E-07 5.8E-05 +step = 77, x = 0.0290, y1and5 = -2.143416391952930E-01 -2.377050934355847E+00, diff1and5 = 2.9E-06 7.8E-05 +step = 79, x = 0.0300, y1and5 = -2.219528408240040E-02 -2.524853935988888E+00, diff1and5 = 2.2E-07 8.5E-06 +step = 80, x = 0.0310, y1and5 = 1.784315561831284E-01 -2.024353412245143E+00, diff1and5 = 4.0E-06 2.9E-04 +step = 82, x = 0.0320, y1and5 = 3.108403881688425E-01 -1.095033084537755E+00, diff1and5 = 7.0E-07 6.6E-05 +step = 84, x = 0.0330, y1and5 = 3.243889870027998E-01 -1.351281531066862E-01, diff1and5 = 1.1E-06 5.7E-05 +step = 86, x = 0.0340, y1and5 = 2.138589333550945E-01 4.561261291820525E-01, diff1and5 = 3.8E-07 1.3E-05 +step = 89, x = 0.0350, y1and5 = 2.177505550254943E-02 6.220899930796396E-01, diff1and5 = 7.3E-08 2.5E-06 +step = 90, x = 0.0360, y1and5 = -1.780029402927223E-01 6.219615062635127E-01, diff1and5 = 2.7E-05 8.8E-04 +step = 93, x = 0.0370, y1and5 = -3.096268590211072E-01 2.255017983309159E-01, diff1and5 = 1.9E-07 8.0E-05 +step = 99, x = 0.0380, y1and5 = -3.246033030857612E-01 -1.450729771171510E+00, diff1and5 = 3.2E-07 6.5E-05 +step = 102, x = 0.0390, y1and5 = -2.144090078932315E-01 -2.234602271558864E+00, diff1and5 = 1.3E-06 8.2E-05 +step = 103, x = 0.0400, y1and5 = -2.225575794426700E-02 -2.385080348224730E+00, diff1and5 = 4.7E-07 9.6E-06 +step = 104, x = 0.0410, y1and5 = 1.783880058813793E-01 -1.886779093725898E+00, diff1and5 = 1.0E-05 1.3E-04 +step = 106, x = 0.0420, y1and5 = 3.107983906300928E-01 -9.604321864034761E-01, diff1and5 = 4.7E-07 2.9E-05 +step = 108, x = 0.0430, y1and5 = 3.243575044289585E-01 -3.145827679016736E-03, diff1and5 = 2.5E-06 9.6E-05 +step = 111, x = 0.0440, y1and5 = 2.138294790615231E-01 5.856716367852630E-01, diff1and5 = 2.5E-07 1.6E-05 +step = 114, x = 0.0450, y1and5 = 2.175063895712489E-02 7.492442901224280E-01, diff1and5 = 1.9E-07 1.1E-06 +step = 115, x = 0.0460, y1and5 = -1.780245999100694E-01 7.466802560157104E-01, diff1and5 = 2.8E-05 9.8E-04 +step = 118, x = 0.0470, y1and5 = -3.096432354425651E-01 3.483520547215561E-01, diff1and5 = 3.3E-07 9.6E-05 +step = 123, x = 0.0480, y1and5 = -3.246172295071914E-01 -1.330381955254372E+00, diff1and5 = 3.8E-08 4.6E-05 +step = 126, x = 0.0490, y1and5 = -2.144228860345288E-01 -2.116620164596746E+00, diff1and5 = 6.7E-07 2.0E-04 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 1511 +Number of Jacobian evaluations = 126 +Number of factorizations = 166 +Number of lin sys solutions = 461 +Number of performed steps = 166 +Number of accepted steps = 127 +Number of rejected steps = 6 +Number of iterations (maximum) = 5 +y1to3 = -2.226517863103583E-02 3.068700099790518E+00 2.898340496735758E+00 +y4to5 = 2.033525394061642E+00 -2.269179795792819E+00 +h = 7.789307871956906E-04 diff --git a/russell_ode/data/russell_radau5_hairer_wanner_eq1.txt b/russell_ode/data/russell_radau5_hairer_wanner_eq1.txt new file mode 100644 index 00000000..24e09b15 --- /dev/null +++ b/russell_ode/data/russell_radau5_hairer_wanner_eq1.txt @@ -0,0 +1,21 @@ + +running 1 test +step = 0, x = 0.00, y = 0.000000000000000E+00 +step = 12, x = 0.20, y = 9.836063882718112E-01 +step = 14, x = 0.40, y = 9.284837278344403E-01 +step = 14, x = 0.60, y = 8.362817229180394E-01 +step = 15, x = 0.80, y = 7.107954474739065E-01 +step = 15, x = 1.00, y = 5.568498958591173E-01 +step = 15, x = 1.20, y = 3.807997101828214E-01 +step = 15, x = 1.40, y = 1.896857914831053E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 67 +Number of Jacobian evaluations = 1 +Number of factorizations = 13 +Number of lin sys solutions = 17 +Number of performed steps = 15 +Number of accepted steps = 15 +Number of rejected steps = 0 +Number of iterations (maximum) = 2 +y = 9.068021382386660E-02 +h = 1.272673814375498E+00 diff --git a/russell_ode/data/russell_radau5_hairer_wanner_eq1_debug.txt b/russell_ode/data/russell_radau5_hairer_wanner_eq1_debug.txt new file mode 100644 index 00000000..ee667482 --- /dev/null +++ b/russell_ode/data/russell_radau5_hairer_wanner_eq1_debug.txt @@ -0,0 +1,30 @@ + +running 1 test +step = 1, newt = 1, ldw = 2.380787522481423E+01, h = 1.000000000000000E-04 +step = 1, newt = 2, ldw = 1.873911326232366E-15, h = 1.000000000000000E-04 +step = 2, newt = 1, ldw = 2.678969181971517E-04, h = 8.000000000000000E-04 +step = 3, newt = 1, ldw = 9.620335401212540E-01, h = 6.400000000000000E-03 +step = 4, newt = 1, ldw = 4.960011319669094E+00, h = 9.175315123155635E-03 +step = 5, newt = 1, ldw = 3.715067327322871E+00, h = 9.175315123155635E-03 +step = 6, newt = 1, ldw = 5.743754221374824E+00, h = 1.268482689579053E-02 +step = 7, newt = 1, ldw = 3.977586037047180E+00, h = 1.268482689579053E-02 +step = 8, newt = 1, ldw = 5.900855962630732E+00, h = 1.802519034150934E-02 +step = 9, newt = 1, ldw = 6.700127424361154E+00, h = 2.245393279005213E-02 +step = 9, newt = 2, ldw = 4.181281930736301E-14, h = 2.245393279005213E-02 +step = 10, newt = 1, ldw = 5.276818106904297E+00, h = 2.765273524751432E-02 +step = 11, newt = 1, ldw = 5.605282816313379E+00, h = 4.104314881966915E-02 +step = 12, newt = 1, ldw = 5.065318111734848E+00, h = 6.502285859140716E-02 +step = 13, newt = 1, ldw = 3.636739773453130E+00, h = 1.297895101230809E-01 +step = 14, newt = 1, ldw = 5.652222379600533E-01, h = 4.206510426485264E-01 +step = 15, newt = 1, ldw = 2.691177218161395E+01, h = 7.243412974003482E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 67 +Number of Jacobian evaluations = 1 +Number of factorizations = 13 +Number of lin sys solutions = 17 +Number of performed steps = 15 +Number of accepted steps = 15 +Number of rejected steps = 0 +Number of iterations (maximum) = 2 +y = 9.068021382386660E-02 +h = 1.272673814375498E+00 diff --git a/russell_ode/data/russell_radau5_robertson.txt b/russell_ode/data/russell_radau5_robertson.txt new file mode 100644 index 00000000..68505706 --- /dev/null +++ b/russell_ode/data/russell_radau5_robertson.txt @@ -0,0 +1,29 @@ + +running 1 test +step = 0, x = 0.00, y = 1.000000000000000E+00 0.000000000000000E+00 0.000000000000000E+00 +step = 1, x = 0.00, y = 9.999999600000008E-01 3.999998320000049E-08 1.599999952000000E-14 +step = 2, x = 0.00, y = 9.999996400000648E-01 3.599882716565646E-07 1.166354336752984E-11 +step = 3, x = 0.00, y = 9.999970800042659E-01 2.913787382334434E-06 6.208351820681376E-09 +step = 4, x = 0.00, y = 9.999863148909263E-01 1.307919521095018E-05 6.059138628061583E-07 +step = 5, x = 0.00, y = 9.999755499712033E-01 2.135889538410894E-05 3.091133412621324E-06 +step = 6, x = 0.00, y = 9.999647854413735E-01 2.724901208912351E-05 7.965546537412103E-06 +step = 7, x = 0.00, y = 9.999478453625212E-01 3.255484201349525E-05 1.959979546529435E-05 +step = 8, x = 0.00, y = 9.999253573022419E-01 3.530902486677610E-05 3.933367289134942E-05 +step = 9, x = 0.00, y = 9.999028742076057E-01 3.614958793290999E-05 6.097620446139352E-05 +step = 10, x = 0.00, y = 9.998490892191770E-01 3.647645985151716E-05 1.144343209714243E-04 +step = 11, x = 0.01, y = 9.997567898163787E-01 3.647881802650773E-05 2.067313655947322E-04 +step = 12, x = 0.01, y = 9.995023134527692E-01 3.643227719665634E-05 4.612542700341240E-04 +step = 13, x = 0.05, y = 9.982091577137117E-01 3.619191783745304E-05 1.754650368450902E-03 +step = 14, x = 0.18, y = 9.930472117086886E-01 3.525604364525154E-05 6.917532247666168E-03 +step = 15, x = 0.30, y = 9.886740138499883E-01 3.447720471782071E-05 1.129150894529388E-02 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 88 +Number of Jacobian evaluations = 8 +Number of factorizations = 15 +Number of lin sys solutions = 24 +Number of performed steps = 17 +Number of accepted steps = 15 +Number of rejected steps = 1 +Number of iterations (maximum) = 2 +y = 9.886740138499883E-01 3.447720471782071E-05 +h = 8.160578540023586E-01 diff --git a/russell_ode/data/russell_radau5_robertson_debug.txt b/russell_ode/data/russell_radau5_robertson_debug.txt new file mode 100644 index 00000000..0589b8da --- /dev/null +++ b/russell_ode/data/russell_radau5_robertson_debug.txt @@ -0,0 +1,37 @@ + +running 1 test +step = 1, newt = 1, ldw = 5.110074806379854E+00, h = 1.000000000000000E-06 +step = 1, newt = 2, ldw = 1.225505168603163E-06, h = 1.000000000000000E-06 +step = 2, newt = 1, ldw = 4.007988749276218E-08, h = 8.000000000000000E-06 +step = 3, newt = 1, ldw = 1.186679516763551E-03, h = 6.400000000000000E-05 +step = 4, newt = 1, ldw = 1.050182079933363E+01, h = 4.041220301517845E-04 +step = 5, newt = 1, ldw = 1.794588455294932E+00, h = 2.691302105629639E-04 +step = 6, newt = 1, ldw = 4.425316731967755E+00, h = 2.691302105629639E-04 +step = 7, newt = 1, ldw = 1.333759092903399E+00, h = 2.691302105629639E-04 +step = 8, newt = 1, ldw = 4.213092607005894E-01, h = 4.235640953741905E-04 +step = 9, newt = 1, ldw = 1.443689406690273E+01, h = 1.022273799181781E-03 +step = 9, newt = 2, ldw = 7.697798879046363E+00, h = 1.022273799181781E-03 +step = 10, newt = 1, ldw = 1.816083490320218E+00, h = 5.623781872578592E-04 +step = 10, newt = 2, ldw = 3.383615888301965E-02, h = 5.623781872578592E-04 +step = 11, newt = 1, ldw = 6.879082974836300E-01, h = 5.623781872578592E-04 +step = 12, newt = 1, ldw = 2.778605634181009E+00, h = 1.345864838724483E-03 +step = 12, newt = 2, ldw = 6.941901766258624E-02, h = 1.345864838724483E-03 +step = 13, newt = 1, ldw = 3.148269813836465E+00, h = 2.311325748539522E-03 +step = 13, newt = 2, ldw = 5.344836596832576E-02, h = 2.311325748539522E-03 +step = 14, newt = 1, ldw = 1.694875050737327E+00, h = 6.383707167442246E-03 +step = 14, newt = 2, ldw = 2.134263187322059E-02, h = 6.383707167442246E-03 +step = 15, newt = 1, ldw = 6.577883260622573E-01, h = 3.269640450524228E-02 +step = 16, newt = 1, ldw = 1.245612221433343E+00, h = 1.348771470917645E-01 +step = 16, newt = 2, ldw = 5.044276431425990E-02, h = 1.348771470917645E-01 +step = 17, newt = 1, ldw = 7.574602063262065E-02, h = 1.199568395467082E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 88 +Number of Jacobian evaluations = 8 +Number of factorizations = 15 +Number of lin sys solutions = 24 +Number of performed steps = 17 +Number of accepted steps = 15 +Number of rejected steps = 1 +Number of iterations (maximum) = 2 +y = 9.886740138499883E-01 3.447720471782071E-05 +h = 8.160578540023586E-01 diff --git a/russell_ode/data/russell_radau5_robertson_small_h.txt b/russell_ode/data/russell_radau5_robertson_small_h.txt new file mode 100644 index 00000000..5fab6e65 --- /dev/null +++ b/russell_ode/data/russell_radau5_robertson_small_h.txt @@ -0,0 +1,167 @@ + +running 1 test +step = 1, newt = 1, ldw = 5.713237318584358E-06, h = 1.000000000000000E-06 +step = 2, newt = 1, ldw = 9.549126042124232E-10, h = 8.000000000000000E-06 +step = 3, newt = 1, ldw = 1.411225363580558E-09, h = 6.400000000000000E-05 +step = 4, newt = 1, ldw = 4.107820729172961E-05, h = 5.120000000000000E-04 +step = 5, newt = 1, ldw = 7.224709706516977E-01, h = 4.096000000000000E-03 +step = 5, newt = 2, ldw = 5.688537757889905E+01, h = 4.096000000000000E-03 +step = 6, newt = 1, ldw = 5.535184873130312E-03, h = 2.048000000000000E-03 +step = 7, newt = 1, ldw = 2.183312157041306E-01, h = 2.048000000000000E-03 +step = 7, newt = 2, ldw = 7.939700994552124E+00, h = 2.048000000000000E-03 +step = 8, newt = 1, ldw = 2.064385209642968E-02, h = 1.024000000000000E-03 +step = 9, newt = 1, ldw = 1.851306702932678E+00, h = 1.024000000000000E-03 +step = 9, newt = 2, ldw = 3.418799139342428E+02, h = 1.024000000000000E-03 +step = 10, newt = 1, ldw = 1.511872801736419E+00, h = 5.120000000000000E-04 +step = 10, newt = 2, ldw = 5.326319203666440E+02, h = 5.120000000000000E-04 +step = 11, newt = 1, ldw = 2.833630003527899E-02, h = 2.560000000000000E-04 +step = 12, newt = 1, ldw = 7.979474595956246E-01, h = 1.353638004672386E-04 +step = 12, newt = 2, ldw = 1.318080076207844E+01, h = 1.353638004672386E-04 +step = 13, newt = 1, ldw = 6.269531353120403E-01, h = 6.768190023361932E-05 +step = 13, newt = 2, ldw = 7.885583465344126E+00, h = 6.768190023361932E-05 +step = 14, newt = 1, ldw = 9.452759342347168E-02, h = 3.384095011680966E-05 +step = 14, newt = 2, ldw = 3.703456575147211E-02, h = 3.384095011680966E-05 +step = 15, newt = 1, ldw = 9.463250538246909E-02, h = 7.988264928398357E-06 +step = 15, newt = 2, ldw = 1.982352214154815E-02, h = 7.988264928398357E-06 +step = 16, newt = 1, ldw = 3.239954519605391E-01, h = 5.459710401840634E-06 +step = 16, newt = 2, ldw = 2.855394028442613E-01, h = 5.459710401840634E-06 +step = 17, newt = 1, ldw = 5.515365611883710E-02, h = 3.003522188671522E-06 +step = 17, newt = 2, ldw = 7.214712288655393E-03, h = 3.003522188671522E-06 +step = 18, newt = 1, ldw = 3.507451382445620E-01, h = 3.003522188671522E-06 +step = 18, newt = 2, ldw = 2.488057504902582E-01, h = 3.003522188671522E-06 +step = 19, newt = 1, ldw = 7.456625523529180E-02, h = 1.958863891429376E-06 +step = 19, newt = 2, ldw = 1.437519505873908E-02, h = 1.958863891429376E-06 +step = 20, newt = 1, ldw = 1.129088123793107E+00, h = 1.958863891429376E-06 +step = 20, newt = 2, ldw = 1.305205610461479E+00, h = 1.958863891429376E-06 +step = 21, newt = 1, ldw = 9.429573342566286E-02, h = 9.794319457146879E-07 +step = 21, newt = 2, ldw = 1.277908234821347E-02, h = 9.794319457146879E-07 +step = 22, newt = 1, ldw = 7.611003464369001E-01, h = 9.794319457146879E-07 +step = 22, newt = 2, ldw = 4.432618503494946E-01, h = 9.794319457146879E-07 +step = 23, newt = 1, ldw = 2.239261384052829E-01, h = 7.034057507633498E-07 +step = 23, newt = 2, ldw = 4.821643900575767E-02, h = 7.034057507633498E-07 +step = 24, newt = 1, ldw = 2.016291416558396E+00, h = 5.891577600070017E-07 +step = 24, newt = 2, ldw = 1.545030446236857E+00, h = 5.891577600070017E-07 +step = 25, newt = 1, ldw = 2.640301674527272E-01, h = 3.241103052301954E-07 +step = 25, newt = 2, ldw = 3.338633177356556E-02, h = 3.241103052301954E-07 +step = 26, newt = 1, ldw = 1.522350659488225E+00, h = 3.086307641888090E-07 +step = 26, newt = 2, ldw = 6.814228580930575E-01, h = 3.086307641888090E-07 +step = 26, newt = 3, ldw = 2.349473133392905E-01, h = 3.086307641888090E-07 +step = 26, newt = 4, ldw = 5.210824005193573E-02, h = 3.086307641888090E-07 +step = 27, newt = 1, ldw = 3.799990706196685E-01, h = 2.084878525702729E-07 +step = 27, newt = 2, ldw = 5.457815644145110E-02, h = 2.084878525702729E-07 +step = 28, newt = 1, ldw = 2.024286080691704E+00, h = 1.798336446925473E-07 +step = 28, newt = 2, ldw = 7.826846013238703E-01, h = 1.798336446925473E-07 +step = 28, newt = 3, ldw = 2.353500555876765E-01, h = 1.798336446925473E-07 +step = 28, newt = 4, ldw = 4.530280230803961E-02, h = 1.798336446925473E-07 +step = 29, newt = 1, ldw = 4.856578888358377E-01, h = 1.184950959010905E-07 +step = 29, newt = 2, ldw = 5.712671517944685E-02, h = 1.184950959010905E-07 +step = 30, newt = 1, ldw = 1.965058780039117E+00, h = 1.031979132281593E-07 +step = 30, newt = 2, ldw = 5.583839432557870E-01, h = 1.031979132281593E-07 +step = 30, newt = 3, ldw = 1.227945951998831E-01, h = 1.031979132281593E-07 +step = 30, newt = 4, ldw = 1.725700354356943E-02, h = 1.031979132281593E-07 +step = 31, newt = 1, ldw = 6.332650257065936E-01, h = 7.415329799992940E-08 +step = 31, newt = 2, ldw = 7.159185196401813E-02, h = 7.415329799992940E-08 +step = 32, newt = 1, ldw = 2.182971710342156E+00, h = 6.216749326865212E-08 +step = 32, newt = 2, ldw = 5.107019542360752E-01, h = 6.216749326865212E-08 +step = 32, newt = 3, ldw = 9.305787270527494E-02, h = 6.216749326865212E-08 +step = 33, newt = 1, ldw = 9.301678867338103E-01, h = 4.843179381465524E-08 +step = 33, newt = 2, ldw = 1.091900273355139E-01, h = 4.843179381465524E-08 +step = 34, newt = 1, ldw = 2.318363930695775E+00, h = 3.670453510967734E-08 +step = 34, newt = 2, ldw = 4.305895750391350E-01, h = 3.670453510967734E-08 +step = 34, newt = 3, ldw = 6.362288571033180E-02, h = 3.670453510967734E-08 +step = 35, newt = 1, ldw = 2.247998360578580E+00, h = 1.821592603741297E-08 +step = 35, newt = 2, ldw = 2.775654255533950E-01, h = 1.821592603741297E-08 +step = 35, newt = 3, ldw = 2.888189218016722E-02, h = 1.821592603741297E-08 +step = 36, newt = 1, ldw = 1.974934301863113E+00, h = 1.064267467598590E-08 +step = 36, newt = 2, ldw = 2.007230940703124E-01, h = 1.064267467598590E-08 +step = 37, newt = 1, ldw = 2.544263026625782E+00, h = 7.252120972034658E-09 +step = 37, newt = 2, ldw = 2.799854451462886E-01, h = 7.252120972034658E-09 +step = 37, newt = 3, ldw = 2.529667157091523E-02, h = 7.252120972034658E-09 +step = 38, newt = 1, ldw = 2.484914027494926E+00, h = 4.492688244570796E-09 +step = 38, newt = 2, ldw = 2.427564460412156E-01, h = 4.492688244570796E-09 +step = 39, newt = 1, ldw = 2.762475172033403E+00, h = 3.002198771549832E-09 +step = 39, newt = 2, ldw = 2.706279629978121E-01, h = 3.002198771549832E-09 +step = 40, newt = 1, ldw = 2.832273397983148E+00, h = 1.966022679603298E-09 +step = 40, newt = 2, ldw = 2.661076871352192E-01, h = 1.966022679603298E-09 +step = 41, newt = 1, ldw = 2.853974292068733E+00, h = 1.306256885046503E-09 +step = 41, newt = 2, ldw = 2.623089508885884E-01, h = 1.306256885046503E-09 +step = 42, newt = 1, ldw = 2.900221148536593E+00, h = 8.768185855134607E-10 +step = 42, newt = 2, ldw = 2.645550255944251E-01, h = 8.768185855134607E-10 +step = 43, newt = 1, ldw = 2.934290654563067E+00, h = 5.897332567717323E-10 +step = 43, newt = 2, ldw = 2.657094886183935E-01, h = 5.897332567717323E-10 +step = 44, newt = 1, ldw = 2.953465450777375E+00, h = 3.974627531569095E-10 +step = 44, newt = 2, ldw = 2.659039432929809E-01, h = 3.974627531569095E-10 +step = 45, newt = 1, ldw = 2.967376833070540E+00, h = 2.684253824364300E-10 +step = 45, newt = 2, ldw = 2.662287390417422E-01, h = 2.684253824364300E-10 +step = 46, newt = 1, ldw = 2.977634165886050E+00, h = 1.814947218560266E-10 +step = 46, newt = 2, ldw = 2.665336147107780E-01, h = 1.814947218560266E-10 +step = 47, newt = 1, ldw = 2.984551675770700E+00, h = 1.228071769396238E-10 +step = 47, newt = 2, ldw = 2.667153165466565E-01, h = 1.228071769396238E-10 +step = 48, newt = 1, ldw = 2.989280122847388E+00, h = 8.314080981554411E-11 +step = 48, newt = 2, ldw = 2.668369994801637E-01, h = 8.314080981554411E-11 +step = 49, newt = 1, ldw = 2.992669984272221E+00, h = 5.630670969718946E-11 +step = 49, newt = 2, ldw = 2.669269649891055E-01, h = 5.630670969718946E-11 +step = 50, newt = 1, ldw = 2.995292844073223E+00, h = 3.814200297085605E-11 +step = 50, newt = 2, ldw = 2.669914052700305E-01, h = 3.814200297085605E-11 +step = 51, newt = 1, ldw = 2.997810990259253E+00, h = 2.584044664639865E-11 +step = 51, newt = 2, ldw = 2.670434656886391E-01, h = 2.584044664639865E-11 +step = 52, newt = 1, ldw = 3.001360575310683E+00, h = 1.750657207889799E-11 +step = 52, newt = 2, ldw = 2.671084029476940E-01, h = 1.750657207889799E-11 +step = 53, newt = 1, ldw = 3.008648835561798E+00, h = 1.185832044573550E-11 +step = 53, newt = 2, ldw = 2.672585670643604E-01, h = 1.185832044573550E-11 +step = 54, newt = 1, ldw = 3.027838574037472E+00, h = 8.027174894304359E-12 +step = 54, newt = 2, ldw = 2.677764060374220E-01, h = 8.027174894304359E-12 +step = 55, newt = 1, ldw = 3.088821934668522E+00, h = 5.422524811697307E-12 +step = 55, newt = 2, ldw = 2.699543206905203E-01, h = 5.422524811697307E-12 +step = 56, newt = 1, ldw = 3.328302464269586E+00, h = 3.636640311174440E-12 +step = 56, newt = 2, ldw = 2.806136775219398E-01, h = 3.636640311174440E-12 +step = 57, newt = 1, ldw = 2.862338196292765E+00, h = 2.368371140272361E-12 +step = 57, newt = 2, ldw = 2.124778328378240E-01, h = 2.368371140272361E-12 +step = 58, newt = 1, ldw = 3.019918842078330E+00, h = 1.708696810038368E-12 +step = 58, newt = 2, ldw = 2.436480743217157E-01, h = 1.708696810038368E-12 +step = 59, newt = 1, ldw = 3.165015855773361E+00, h = 1.188301059994388E-12 +step = 59, newt = 2, ldw = 2.592082895982138E-01, h = 1.188301059994388E-12 +step = 60, newt = 1, ldw = 3.137748677200451E+00, h = 8.144089923824477E-13 +step = 60, newt = 2, ldw = 2.536406296288061E-01, h = 8.144089923824477E-13 +step = 61, newt = 1, ldw = 3.123456848142392E+00, h = 5.618734579641469E-13 +step = 61, newt = 2, ldw = 2.524646503415428E-01, h = 5.618734579641469E-13 +step = 62, newt = 1, ldw = 3.128709160777779E+00, h = 3.879922829537460E-13 +step = 62, newt = 2, ldw = 2.532954494336762E-01, h = 3.879922829537460E-13 +step = 63, newt = 1, ldw = 3.129766416816868E+00, h = 2.676725339843544E-13 +step = 63, newt = 2, ldw = 2.533279713103440E-01, h = 2.676725339843544E-13 +step = 64, newt = 1, ldw = 3.128995216959199E+00, h = 1.846680970816026E-13 +step = 64, newt = 2, ldw = 2.532222899654013E-01, h = 1.846680970816026E-13 +step = 65, newt = 1, ldw = 3.128977990148831E+00, h = 1.274173745952554E-13 +step = 65, newt = 2, ldw = 2.532326447026642E-01, h = 1.274173745952554E-13 +step = 66, newt = 1, ldw = 3.129080262268600E+00, h = 8.791404984946950E-14 +step = 66, newt = 2, ldw = 2.532442627193796E-01, h = 8.791404984946950E-14 +step = 67, newt = 1, ldw = 3.129072668137014E+00, h = 6.065730745333961E-14 +step = 67, newt = 2, ldw = 2.532415141789559E-01, h = 6.065730745333961E-14 +step = 68, newt = 1, ldw = 3.129063972922112E+00, h = 4.185136430985568E-14 +step = 68, newt = 2, ldw = 2.532405151538102E-01, h = 4.185136430985568E-14 +step = 69, newt = 1, ldw = 3.129067978300174E+00, h = 2.887596915087791E-14 +step = 69, newt = 2, ldw = 2.532410196357686E-01, h = 2.887596915087791E-14 +step = 70, newt = 1, ldw = 3.129069822180785E+00, h = 1.992339383572132E-14 +step = 70, newt = 2, ldw = 2.532410980893799E-01, h = 1.992339383572132E-14 +step = 71, newt = 1, ldw = 3.129069996401156E+00, h = 1.374643510000513E-14 +step = 71, newt = 2, ldw = 2.532410426436356E-01, h = 1.374643510000513E-14 +step = 72, newt = 1, ldw = 3.129070376127277E+00, h = 9.484553812285281E-15 +step = 72, newt = 2, ldw = 2.532410519958102E-01, h = 9.484553812285281E-15 +step = 73, newt = 1, ldw = 3.129070737373394E+00, h = 6.544006711359746E-15 +step = 73, newt = 2, ldw = 2.532410654413505E-01, h = 6.544006711359746E-15 +step = 74, newt = 1, ldw = 3.129070941638619E+00, h = 4.515133271322660E-15 +step = 74, newt = 2, ldw = 2.532410679690474E-01, h = 4.515133271322660E-15 +step = 75, newt = 1, ldw = 3.129071076495209E+00, h = 3.115282382680138E-15 +step = 75, newt = 2, ldw = 2.532410697645308E-01, h = 3.115282382680138E-15 +ERROR: THE STEPSIZE BECOMES TOO SMALL +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 520 +Number of Jacobian evaluations = 57 +Number of factorizations = 75 +Number of lin sys solutions = 153 +Number of performed steps = 75 +Number of accepted steps = 60 +Number of rejected steps = 4 +Number of iterations (maximum) = 4 +y = -1.603420554879705E+03 -4.810051809113435E+06 +h = 2.149434768637158E-15 diff --git a/russell_ode/data/russell_radau5_van_der_pol.txt b/russell_ode/data/russell_radau5_van_der_pol.txt new file mode 100644 index 00000000..ee836044 --- /dev/null +++ b/russell_ode/data/russell_radau5_van_der_pol.txt @@ -0,0 +1,24 @@ + +running 1 test +step = 0, x = 0.00, y = 2.000000000000000E+00 -6.000000000000000E-01 +step = 14, x = 0.20, y = 1.858193270854655E+00 -7.575101255756231E-01 +step = 15, x = 0.40, y = 1.693203123722821E+00 -9.069127086067541E-01 +step = 17, x = 0.60, y = 1.484568811304371E+00 -1.233096041433422E+00 +step = 26, x = 0.80, y = 1.083920681222182E+00 -6.195489374899131E+00 +step = 127, x = 1.00, y = -1.863647684640764E+00 7.535336997437251E-01 +step = 128, x = 1.20, y = -1.699726525700276E+00 8.997803216820141E-01 +step = 130, x = 1.40, y = -1.493380180170898E+00 1.213868996692300E+00 +step = 137, x = 1.60, y = -1.120792932708669E+00 4.374425849430548E+00 +step = 241, x = 1.80, y = 1.869055755232369E+00 -7.495776558055867E-01 +step = 242, x = 2.00, y = 1.706163410178069E+00 -8.927971289309894E-01 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 2249 +Number of Jacobian evaluations = 162 +Number of factorizations = 253 +Number of lin sys solutions = 668 +Number of performed steps = 280 +Number of accepted steps = 242 +Number of rejected steps = 8 +Number of iterations (maximum) = 6 +y = 1.706163410178069E+00 -8.927971289309896E-01 +h = 1.510987114560156E-01 diff --git a/russell_ode/data/russell_radau5_van_der_pol_debug.txt b/russell_ode/data/russell_radau5_van_der_pol_debug.txt new file mode 100644 index 00000000..0e10fd76 --- /dev/null +++ b/russell_ode/data/russell_radau5_van_der_pol_debug.txt @@ -0,0 +1,681 @@ + +running 1 test +step = 1, newt = 1, ldw = 2.436352665990826E+02, h = 1.000000000000000E-06 +step = 1, newt = 2, ldw = 3.880290088842763E-05, h = 1.000000000000000E-06 +step = 2, newt = 1, ldw = 3.935169421326134E+01, h = 1.000000000000000E-07 +step = 3, newt = 1, ldw = 5.065177021872400E-02, h = 1.000000000000000E-07 +step = 4, newt = 1, ldw = 3.222282661759627E+00, h = 3.885735108906138E-07 +step = 5, newt = 1, ldw = 6.519580022031894E+00, h = 4.972810029442541E-07 +step = 6, newt = 1, ldw = 5.807917778678124E+00, h = 7.083509253538557E-07 +step = 6, newt = 2, ldw = 3.209234662148081E-06, h = 7.083509253538557E-07 +step = 7, newt = 1, ldw = 4.089966344085277E+00, h = 1.073623851057528E-06 +step = 8, newt = 1, ldw = 3.364871108708860E+00, h = 2.290473452678114E-06 +step = 9, newt = 1, ldw = 1.700114059337336E+00, h = 7.536689063977756E-06 +step = 10, newt = 1, ldw = 6.970135369886608E-01, h = 4.422664055361883E-05 +step = 11, newt = 1, ldw = 9.017945151084504E-02, h = 3.538131244289506E-04 +step = 12, newt = 1, ldw = 1.629341585586337E-01, h = 2.830504995431605E-03 +step = 13, newt = 1, ldw = 7.271934637313561E-01, h = 2.264403996345284E-02 +step = 13, newt = 2, ldw = 1.578498075432852E-02, h = 2.264403996345284E-02 +step = 14, newt = 1, ldw = 3.737282774008689E-01, h = 1.059258244600457E-01 +step = 14, newt = 2, ldw = 3.422760767869296E-02, h = 1.059258244600457E-01 +step = 15, newt = 1, ldw = 2.623588146114826E+01, h = 4.141772841896277E-01 +step = 15, newt = 2, ldw = 1.488865812436664E+01, h = 4.141772841896277E-01 +step = 16, newt = 1, ldw = 3.132626173486828E+00, h = 2.278492028968919E-01 +step = 16, newt = 2, ldw = 8.767206658794571E-01, h = 2.278492028968919E-01 +step = 16, newt = 3, ldw = 2.408783656427306E-01, h = 2.278492028968919E-01 +step = 16, newt = 4, ldw = 6.510927802164015E-02, h = 2.278492028968919E-01 +step = 16, newt = 5, ldw = 1.726194460565636E-02, h = 2.278492028968919E-01 +step = 17, newt = 1, ldw = 2.601024840969384E+01, h = 2.278492028968919E-01 +step = 17, newt = 2, ldw = 1.026076153511902E+01, h = 2.278492028968919E-01 +step = 18, newt = 1, ldw = 7.069478522734184E+00, h = 1.350257782459604E-01 +step = 18, newt = 2, ldw = 1.377857661179124E+00, h = 1.350257782459604E-01 +step = 18, newt = 3, ldw = 3.167582455562805E-01, h = 1.350257782459604E-01 +step = 18, newt = 4, ldw = 7.448580326651484E-02, h = 1.350257782459604E-01 +step = 18, newt = 5, ldw = 1.721897842034092E-02, h = 1.350257782459604E-01 +step = 19, newt = 1, ldw = 1.643593739809761E+01, h = 1.350257782459604E-01 +step = 19, newt = 2, ldw = 5.288488095383715E+00, h = 1.350257782459604E-01 +step = 20, newt = 1, ldw = 7.550942582275325E+00, h = 1.001464452905111E-01 +step = 20, newt = 2, ldw = 1.626911072876759E+00, h = 1.001464452905111E-01 +step = 20, newt = 3, ldw = 3.936137909505081E-01, h = 1.001464452905111E-01 +step = 20, newt = 4, ldw = 9.611025392147547E-02, h = 1.001464452905111E-01 +step = 20, newt = 5, ldw = 2.301210122291745E-02, h = 1.001464452905111E-01 +step = 21, newt = 1, ldw = 2.475834989530263E+01, h = 1.001464452905111E-01 +step = 21, newt = 2, ldw = 8.718396825558139E+00, h = 1.001464452905111E-01 +step = 22, newt = 1, ldw = 8.390411043003040E+00, h = 6.557565230517032E-02 +step = 22, newt = 2, ldw = 1.654160471797604E+00, h = 6.557565230517032E-02 +step = 22, newt = 3, ldw = 3.725755229618854E-01, h = 6.557565230517032E-02 +step = 22, newt = 4, ldw = 8.510014557170310E-02, h = 6.557565230517032E-02 +step = 22, newt = 5, ldw = 1.906672198543293E-02, h = 6.557565230517032E-02 +step = 23, newt = 1, ldw = 2.257712889941312E+01, h = 6.557565230517032E-02 +step = 23, newt = 2, ldw = 7.368542240317368E+00, h = 6.557565230517032E-02 +step = 24, newt = 1, ldw = 9.091830670808221E+00, h = 4.620885633925620E-02 +step = 24, newt = 2, ldw = 1.834569787493703E+00, h = 4.620885633925620E-02 +step = 24, newt = 3, ldw = 4.144241481273402E-01, h = 4.620885633925620E-02 +step = 24, newt = 4, ldw = 9.445367126877033E-02, h = 4.620885633925620E-02 +step = 24, newt = 5, ldw = 2.109253746077919E-02, h = 4.620885633925620E-02 +step = 25, newt = 1, ldw = 2.726450871842817E+01, h = 4.620885633925620E-02 +step = 25, newt = 2, ldw = 9.111093916864627E+00, h = 4.620885633925620E-02 +step = 26, newt = 1, ldw = 9.873103539012247E+00, h = 3.119889966465076E-02 +step = 26, newt = 2, ldw = 1.911336300507966E+00, h = 3.119889966465076E-02 +step = 26, newt = 3, ldw = 4.157097723819355E-01, h = 3.119889966465076E-02 +step = 26, newt = 4, ldw = 9.131318034795666E-02, h = 3.119889966465076E-02 +step = 26, newt = 5, ldw = 1.964843890133976E-02, h = 3.119889966465076E-02 +step = 27, newt = 1, ldw = 2.748065753033571E+01, h = 3.119889966465076E-02 +step = 27, newt = 2, ldw = 8.871983690867784E+00, h = 3.119889966465076E-02 +step = 28, newt = 1, ldw = 1.059701044535738E+01, h = 2.164088586664780E-02 +step = 28, newt = 2, ldw = 2.052870811450216E+00, h = 2.164088586664780E-02 +step = 28, newt = 3, ldw = 4.424336853496175E-01, h = 2.164088586664780E-02 +step = 28, newt = 4, ldw = 9.606871883415589E-02, h = 2.164088586664780E-02 +step = 28, newt = 5, ldw = 2.042104358303568E-02, h = 2.164088586664780E-02 +step = 29, newt = 1, ldw = 2.905027030532415E+01, h = 2.125655690999314E-02 +step = 29, newt = 2, ldw = 9.175307287363282E+00, h = 2.125655690999314E-02 +step = 30, newt = 1, ldw = 1.154639899223012E+01, h = 1.490438830314256E-02 +step = 30, newt = 2, ldw = 2.213691888601307E+00, h = 1.490438830314256E-02 +step = 30, newt = 3, ldw = 4.710403708373855E-01, h = 1.490438830314256E-02 +step = 30, newt = 4, ldw = 1.009110953546672E-01, h = 1.490438830314256E-02 +step = 30, newt = 5, ldw = 2.115364150470610E-02, h = 1.490438830314256E-02 +step = 31, newt = 1, ldw = 2.962699383473788E+01, h = 1.433808980984527E-02 +step = 31, newt = 2, ldw = 9.060540935084793E+00, h = 1.433808980984527E-02 +step = 32, newt = 1, ldw = 1.253319869412167E+01, h = 1.029289967045404E-02 +step = 32, newt = 2, ldw = 2.398771038720190E+00, h = 1.029289967045404E-02 +step = 32, newt = 3, ldw = 5.075398132830906E-01, h = 1.029289967045404E-02 +step = 32, newt = 4, ldw = 1.079923289604889E-01, h = 1.029289967045404E-02 +step = 32, newt = 5, ldw = 2.247276729597647E-02, h = 1.029289967045404E-02 +step = 33, newt = 1, ldw = 3.012659859134110E+01, h = 9.638278769508709E-03 +step = 33, newt = 2, ldw = 8.915149808236515E+00, h = 9.638278769508709E-03 +step = 34, newt = 1, ldw = 1.361491187811219E+01, h = 7.089615165092199E-03 +step = 34, newt = 2, ldw = 2.603589675024174E+00, h = 7.089615165092199E-03 +step = 34, newt = 3, ldw = 5.490438508105639E-01, h = 7.089615165092199E-03 +step = 34, newt = 4, ldw = 1.163304452099956E-01, h = 7.089615165092199E-03 +step = 34, newt = 5, ldw = 2.409342361052189E-02, h = 7.089615165092199E-03 +step = 35, newt = 1, ldw = 3.054402442133172E+01, h = 6.461975503774283E-03 +step = 35, newt = 2, ldw = 8.761341591361198E+00, h = 6.461975503774283E-03 +step = 36, newt = 1, ldw = 1.470077384520221E+01, h = 4.865054591894151E-03 +step = 36, newt = 2, ldw = 2.810717739882425E+00, h = 4.865054591894151E-03 +step = 36, newt = 3, ldw = 5.914648943947297E-01, h = 4.865054591894151E-03 +step = 36, newt = 4, ldw = 1.249448167091901E-01, h = 4.865054591894151E-03 +step = 36, newt = 5, ldw = 2.578628859369899E-02, h = 4.865054591894151E-03 +step = 37, newt = 1, ldw = 3.079834091857581E+01, h = 4.318130752266890E-03 +step = 37, newt = 2, ldw = 8.568702891480600E+00, h = 4.318130752266890E-03 +step = 38, newt = 1, ldw = 1.578104295747039E+01, h = 3.327844351079868E-03 +step = 38, newt = 2, ldw = 3.020328163989482E+00, h = 3.327844351079868E-03 +step = 38, newt = 3, ldw = 6.350643563574734E-01, h = 3.327844351079868E-03 +step = 38, newt = 4, ldw = 1.339151038071964E-01, h = 3.327844351079868E-03 +step = 38, newt = 5, ldw = 2.757000852511550E-02, h = 3.327844351079868E-03 +step = 39, newt = 1, ldw = 3.085145627697252E+01, h = 2.873666383824403E-03 +step = 39, newt = 2, ldw = 8.317420671349730E+00, h = 2.873666383824403E-03 +step = 40, newt = 1, ldw = 1.688547714725316E+01, h = 2.270444414181189E-03 +step = 40, newt = 2, ldw = 3.239789446316772E+00, h = 2.270444414181189E-03 +step = 40, newt = 3, ldw = 6.816447523741997E-01, h = 2.270444414181189E-03 +step = 40, newt = 4, ldw = 1.436446663366063E-01, h = 2.270444414181189E-03 +step = 40, newt = 5, ldw = 2.952750120865534E-02, h = 2.270444414181189E-03 +step = 41, newt = 1, ldw = 3.061983863353772E+01, h = 1.901235361167906E-03 +step = 41, newt = 2, ldw = 7.967550632523736E+00, h = 1.901235361167906E-03 +step = 41, newt = 3, ldw = 2.177465033481452E+00, h = 1.901235361167906E-03 +step = 42, newt = 1, ldw = 1.722763461227522E+01, h = 1.516052377827367E-03 +step = 42, newt = 2, ldw = 3.227344567799872E+00, h = 1.516052377827367E-03 +step = 42, newt = 3, ldw = 6.642164071588003E-01, h = 1.516052377827367E-03 +step = 42, newt = 4, ldw = 1.368311096658531E-01, h = 1.516052377827367E-03 +step = 42, newt = 5, ldw = 2.746632862475081E-02, h = 1.516052377827367E-03 +step = 43, newt = 1, ldw = 2.904804302583310E+01, h = 1.255403977628761E-03 +step = 43, newt = 2, ldw = 7.246196026496002E+00, h = 1.255403977628761E-03 +step = 43, newt = 3, ldw = 1.896773958418106E+00, h = 1.255403977628761E-03 +step = 43, newt = 4, ldw = 4.896346737235572E-01, h = 1.255403977628761E-03 +step = 43, newt = 5, ldw = 1.225768980039893E-01, h = 1.255403977628761E-03 +step = 43, newt = 6, ldw = 2.993839803269317E-02, h = 1.255403977628761E-03 +step = 44, newt = 1, ldw = 4.004542834213546E+01, h = 7.727582150167698E-04 +step = 44, newt = 2, ldw = 9.416373374253972E+00, h = 7.727582150167698E-04 +step = 44, newt = 3, ldw = 2.448548215424046E+00, h = 7.727582150167698E-04 +step = 44, newt = 4, ldw = 6.302613353699736E-01, h = 7.727582150167698E-04 +step = 45, newt = 1, ldw = 2.387795197912465E+01, h = 6.178927302423208E-04 +step = 45, newt = 2, ldw = 4.005728978368959E+00, h = 6.178927302423208E-04 +step = 45, newt = 3, ldw = 7.782594494974758E-01, h = 6.178927302423208E-04 +step = 45, newt = 4, ldw = 1.519376809666550E-01, h = 6.178927302423208E-04 +step = 45, newt = 5, ldw = 2.871540190329393E-02, h = 6.178927302423208E-04 +step = 46, newt = 1, ldw = 1.659572520838157E+01, h = 4.140194770775987E-04 +step = 46, newt = 2, ldw = 2.828814944606894E+00, h = 4.140194770775987E-04 +step = 46, newt = 3, ldw = 5.216049348548168E-01, h = 4.140194770775987E-04 +step = 46, newt = 4, ldw = 9.454062232686498E-02, h = 4.140194770775987E-04 +step = 46, newt = 5, ldw = 1.645472370272162E-02, h = 4.140194770775987E-04 +step = 47, newt = 1, ldw = 2.185552697333008E+01, h = 3.240566068742182E-04 +step = 47, newt = 2, ldw = 4.354772372493391E+00, h = 3.240566068742182E-04 +step = 47, newt = 3, ldw = 8.949737627922106E-01, h = 3.240566068742182E-04 +step = 47, newt = 4, ldw = 1.769928888482760E-01, h = 3.240566068742182E-04 +step = 47, newt = 5, ldw = 3.321951332277250E-02, h = 3.240566068742182E-04 +step = 48, newt = 1, ldw = 2.376831420831695E+01, h = 2.098105164576718E-04 +step = 48, newt = 2, ldw = 4.150372212679844E+00, h = 2.098105164576718E-04 +step = 48, newt = 3, ldw = 7.510955322165298E-01, h = 2.098105164576718E-04 +step = 48, newt = 4, ldw = 1.275754974764930E-01, h = 2.098105164576718E-04 +step = 48, newt = 5, ldw = 2.004728373613706E-02, h = 2.098105164576718E-04 +step = 49, newt = 1, ldw = 1.386294118276753E+01, h = 1.190103110400204E-04 +step = 49, newt = 2, ldw = 1.594061310603400E+00, h = 1.190103110400204E-04 +step = 49, newt = 3, ldw = 1.834475607378620E-01, h = 1.190103110400204E-04 +step = 49, newt = 4, ldw = 1.882195325959422E-02, h = 1.190103110400204E-04 +step = 50, newt = 1, ldw = 7.562679055855304E+00, h = 7.846458712414815E-05 +step = 50, newt = 2, ldw = 6.239135087492615E-01, h = 7.846458712414815E-05 +step = 50, newt = 3, ldw = 4.685549502009022E-02, h = 7.846458712414815E-05 +step = 51, newt = 1, ldw = 6.670602912680308E+00, h = 6.171837503692623E-05 +step = 51, newt = 2, ldw = 4.811681376616118E-01, h = 6.171837503692623E-05 +step = 51, newt = 3, ldw = 2.916639733070633E-02, h = 6.171837503692623E-05 +step = 52, newt = 1, ldw = 6.581789033661655E+00, h = 4.820594813992682E-05 +step = 52, newt = 2, ldw = 3.896353050866075E-01, h = 4.820594813992682E-05 +step = 52, newt = 3, ldw = 1.850784433126213E-02, h = 4.820594813992682E-05 +step = 53, newt = 1, ldw = 5.961256320618994E+00, h = 3.720304896792119E-05 +step = 53, newt = 2, ldw = 2.750732636672879E-01, h = 3.720304896792119E-05 +step = 54, newt = 1, ldw = 5.709503845774910E+00, h = 2.983153309912547E-05 +step = 54, newt = 2, ldw = 2.158410697196776E-01, h = 2.983153309912547E-05 +step = 55, newt = 1, ldw = 5.518461596798638E+00, h = 2.430479245641477E-05 +step = 55, newt = 2, ldw = 1.758543984556471E-01, h = 2.430479245641477E-05 +step = 56, newt = 1, ldw = 5.412647347265537E+00, h = 2.007826328029185E-05 +step = 56, newt = 2, ldw = 1.487616876047733E-01, h = 2.007826328029185E-05 +step = 57, newt = 1, ldw = 5.353371396750261E+00, h = 1.676635423317771E-05 +step = 57, newt = 2, ldw = 1.295314357749369E-01, h = 1.676635423317771E-05 +step = 58, newt = 1, ldw = 5.318937831790468E+00, h = 1.412635306424373E-05 +step = 58, newt = 2, ldw = 1.154740103722575E-01, h = 1.412635306424373E-05 +step = 59, newt = 1, ldw = 5.300639051516546E+00, h = 1.199261803049633E-05 +step = 59, newt = 2, ldw = 1.050590319282581E-01, h = 1.199261803049633E-05 +step = 60, newt = 1, ldw = 5.292639913374614E+00, h = 1.024596527027310E-05 +step = 60, newt = 2, ldw = 9.724515670985194E-02, h = 1.024596527027310E-05 +step = 61, newt = 1, ldw = 5.290815710666470E+00, h = 8.800122416633654E-06 +step = 61, newt = 2, ldw = 9.131847754845036E-02, h = 8.800122416633654E-06 +step = 62, newt = 1, ldw = 5.292584566049658E+00, h = 7.591662503266102E-06 +step = 62, newt = 2, ldw = 8.678614096922849E-02, h = 7.591662503266102E-06 +step = 63, newt = 1, ldw = 5.296303677586854E+00, h = 6.573127945004764E-06 +step = 63, newt = 2, ldw = 8.329743620120743E-02, h = 6.573127945004764E-06 +step = 64, newt = 1, ldw = 5.300918409620279E+00, h = 5.708488991664934E-06 +step = 64, newt = 2, ldw = 8.059730062449594E-02, h = 5.708488991664934E-06 +step = 65, newt = 1, ldw = 5.305771642248926E+00, h = 4.970010248794752E-06 +step = 65, newt = 2, ldw = 7.849718703304850E-02, h = 4.970010248794752E-06 +step = 66, newt = 1, ldw = 5.310472398976393E+00, h = 4.336049905583411E-06 +step = 66, newt = 2, ldw = 7.685569892288308E-02, h = 4.336049905583411E-06 +step = 67, newt = 1, ldw = 5.314805869542272E+00, h = 3.789497294808984E-06 +step = 67, newt = 2, ldw = 7.556549889427626E-02, h = 3.789497294808984E-06 +step = 68, newt = 1, ldw = 5.318672066283674E+00, h = 3.316651175626244E-06 +step = 68, newt = 2, ldw = 7.454424918431117E-02, h = 3.316651175626244E-06 +step = 69, newt = 1, ldw = 5.322043584177724E+00, h = 2.906406627377686E-06 +step = 69, newt = 2, ldw = 7.372818670472245E-02, h = 2.906406627377686E-06 +step = 70, newt = 1, ldw = 5.324936635420567E+00, h = 2.549660397741805E-06 +step = 70, newt = 2, ldw = 7.306746163244560E-02, h = 2.549660397741805E-06 +step = 71, newt = 1, ldw = 5.327391431872943E+00, h = 2.238872153906572E-06 +step = 71, newt = 2, ldw = 7.252267814659506E-02, h = 2.238872153906572E-06 +step = 72, newt = 1, ldw = 5.329459122079109E+00, h = 1.967737767673291E-06 +step = 72, newt = 2, ldw = 7.206226502209710E-02, h = 1.967737767673291E-06 +step = 73, newt = 1, ldw = 5.331193238957259E+00, h = 1.730943626852938E-06 +step = 73, newt = 2, ldw = 7.166042008339249E-02, h = 1.730943626852938E-06 +step = 74, newt = 1, ldw = 5.332644113462400E+00, h = 1.523979940935047E-06 +step = 74, newt = 2, ldw = 7.129544249710186E-02, h = 1.523979940935047E-06 +step = 75, newt = 1, ldw = 5.333855030361526E+00, h = 1.342997339260276E-06 +step = 75, newt = 2, ldw = 7.094830534533331E-02, h = 1.342997339260276E-06 +step = 76, newt = 1, ldw = 5.334859059568879E+00, h = 1.184695564491441E-06 +step = 76, newt = 2, ldw = 7.060133434748676E-02, h = 1.184695564491441E-06 +step = 77, newt = 1, ldw = 5.335675453002390E+00, h = 1.046236304616230E-06 +step = 77, newt = 2, ldw = 7.023684611729447E-02, h = 1.046236304616230E-06 +step = 78, newt = 1, ldw = 5.336304139125658E+00, h = 9.251745848471268E-07 +step = 78, newt = 2, ldw = 6.983555079604731E-02, h = 9.251745848471268E-07 +step = 79, newt = 1, ldw = 5.336715885889392E+00, h = 8.194049670197306E-07 +step = 79, newt = 2, ldw = 6.937441174716510E-02, h = 8.194049670197306E-07 +step = 80, newt = 1, ldw = 5.336833434628917E+00, h = 7.271203615960886E-07 +step = 80, newt = 2, ldw = 6.882341354420311E-02, h = 7.271203615960886E-07 +step = 81, newt = 1, ldw = 5.336493508721007E+00, h = 6.467828928124181E-07 +step = 81, newt = 2, ldw = 6.814015746700208E-02, h = 6.467828928124181E-07 +step = 82, newt = 1, ldw = 5.335366021573250E+00, h = 5.771085780159380E-07 +step = 82, newt = 2, ldw = 6.725995750410775E-02, h = 5.771085780159380E-07 +step = 83, newt = 1, ldw = 5.332769641042677E+00, h = 5.170719900298279E-07 +step = 83, newt = 2, ldw = 6.607592536119830E-02, h = 5.170719900298279E-07 +step = 84, newt = 1, ldw = 5.327209198015963E+00, h = 4.659474741879379E-07 +step = 84, newt = 2, ldw = 6.439441631302044E-02, h = 4.659474741879379E-07 +step = 85, newt = 1, ldw = 5.315058395765305E+00, h = 4.234326619534066E-07 +step = 85, newt = 2, ldw = 6.182091482951655E-02, h = 4.234326619534066E-07 +step = 86, newt = 1, ldw = 5.286078481077940E+00, h = 3.899981086964948E-07 +step = 86, newt = 2, ldw = 5.740808591703558E-02, h = 3.899981086964948E-07 +step = 87, newt = 1, ldw = 5.203497596638972E+00, h = 3.680271079033273E-07 +step = 87, newt = 2, ldw = 4.822144872210651E-02, h = 3.680271079033273E-07 +step = 88, newt = 1, ldw = 4.498570279876359E+00, h = 3.577078930206454E-07 +step = 88, newt = 2, ldw = 1.830025710041775E-02, h = 3.577078930206454E-07 +step = 89, newt = 1, ldw = 2.230499238895252E+00, h = 3.899350502508790E-07 +step = 89, newt = 2, ldw = 1.136396421978536E-01, h = 3.899350502508790E-07 +step = 90, newt = 1, ldw = 9.231848049941808E+00, h = 3.005667112148398E-07 +step = 90, newt = 2, ldw = 3.799601973028980E-02, h = 3.005667112148398E-07 +step = 91, newt = 1, ldw = 3.749714234431288E+00, h = 2.284289467766866E-07 +step = 91, newt = 2, ldw = 4.134293191578029E-03, h = 2.284289467766866E-07 +step = 92, newt = 1, ldw = 2.890874298794330E+00, h = 1.832352747488011E-07 +step = 93, newt = 1, ldw = 3.154030480810141E+00, h = 1.832352747488011E-07 +step = 93, newt = 2, ldw = 8.238191581182022E-02, h = 1.832352747488011E-07 +step = 94, newt = 1, ldw = 4.418807057757164E+00, h = 2.069547815130774E-07 +step = 94, newt = 2, ldw = 1.676163356136149E-01, h = 2.069547815130774E-07 +step = 95, newt = 1, ldw = 6.027783905048772E+00, h = 2.581820378102271E-07 +step = 95, newt = 2, ldw = 8.940443802744556E-02, h = 2.581820378102271E-07 +step = 96, newt = 1, ldw = 1.726616277384504E+00, h = 1.882248733035727E-07 +step = 96, newt = 2, ldw = 3.051807874820779E-02, h = 1.882248733035727E-07 +step = 97, newt = 1, ldw = 4.765513566194061E+00, h = 1.609940375842837E-07 +step = 97, newt = 2, ldw = 9.068138539185312E-02, h = 1.609940375842837E-07 +step = 98, newt = 1, ldw = 5.300931343942572E+00, h = 1.592398406623307E-07 +step = 98, newt = 2, ldw = 1.079811500503363E-01, h = 1.592398406623307E-07 +step = 99, newt = 1, ldw = 4.073363273792702E+00, h = 1.862829851712705E-07 +step = 99, newt = 2, ldw = 5.651623975687115E-02, h = 1.862829851712705E-07 +step = 100, newt = 1, ldw = 1.790301196605688E+01, h = 2.509790591878295E-07 +step = 100, newt = 2, ldw = 9.889437226074593E-01, h = 2.509790591878295E-07 +step = 100, newt = 3, ldw = 2.825561118826860E-02, h = 2.509790591878295E-07 +step = 101, newt = 1, ldw = 4.328822388529992E+00, h = 1.659469354990381E-07 +step = 101, newt = 2, ldw = 1.198650469664034E-01, h = 1.659469354990381E-07 +step = 102, newt = 1, ldw = 3.938135671239452E+00, h = 1.291251266890169E-07 +step = 102, newt = 2, ldw = 3.941414346741318E-02, h = 1.291251266890169E-07 +step = 103, newt = 1, ldw = 6.587860698357997E+00, h = 1.660286818067649E-07 +step = 103, newt = 2, ldw = 6.713889052396714E-02, h = 1.660286818067649E-07 +step = 104, newt = 1, ldw = 7.083803946780354E+00, h = 1.807848158896437E-07 +step = 104, newt = 2, ldw = 3.356895882292988E-02, h = 1.807848158896437E-07 +step = 105, newt = 1, ldw = 2.015419995715112E+00, h = 2.347984261910251E-07 +step = 105, newt = 2, ldw = 9.885791390702076E-02, h = 2.347984261910251E-07 +step = 106, newt = 1, ldw = 2.121679862675542E+01, h = 2.406409710589756E-07 +step = 106, newt = 2, ldw = 2.081889882200853E-01, h = 2.406409710589756E-07 +step = 107, newt = 1, ldw = 1.031184220781187E+01, h = 1.901861338529596E-07 +step = 107, newt = 2, ldw = 6.928229375841785E-02, h = 1.901861338529596E-07 +step = 108, newt = 1, ldw = 7.664129937014673E+00, h = 1.547173182050026E-07 +step = 108, newt = 2, ldw = 1.704374423491590E-02, h = 1.547173182050026E-07 +step = 109, newt = 1, ldw = 9.338743126205729E+00, h = 1.668359497821760E-07 +step = 109, newt = 2, ldw = 1.428048889137750E-02, h = 1.668359497821760E-07 +step = 110, newt = 1, ldw = 1.127890072772992E+01, h = 1.665857157429237E-07 +step = 110, newt = 2, ldw = 1.016369432263848E-02, h = 1.665857157429237E-07 +step = 111, newt = 1, ldw = 1.134983485151318E+01, h = 1.632964854419789E-07 +step = 111, newt = 2, ldw = 1.592952180944163E-02, h = 1.632964854419789E-07 +step = 112, newt = 1, ldw = 1.106891424211235E+01, h = 1.610752550563100E-07 +step = 112, newt = 2, ldw = 3.433361023868423E-03, h = 1.610752550563100E-07 +step = 113, newt = 1, ldw = 1.089975269690970E+01, h = 1.598105625613794E-07 +step = 113, newt = 2, ldw = 5.440380143555212E-03, h = 1.598105625613794E-07 +step = 114, newt = 1, ldw = 1.079899049160501E+01, h = 1.590274846213328E-07 +step = 114, newt = 2, ldw = 6.666508630345372E-03, h = 1.590274846213328E-07 +step = 115, newt = 1, ldw = 1.073665048114502E+01, h = 1.585515001539855E-07 +step = 115, newt = 2, ldw = 7.411207267881252E-03, h = 1.585515001539855E-07 +step = 116, newt = 1, ldw = 1.069863709700336E+01, h = 1.582616338890596E-07 +step = 116, newt = 2, ldw = 7.867486463557428E-03, h = 1.582616338890596E-07 +step = 117, newt = 1, ldw = 1.067501383116749E+01, h = 1.580857603739003E-07 +step = 117, newt = 2, ldw = 8.148573372832953E-03, h = 1.580857603739003E-07 +step = 118, newt = 1, ldw = 1.065979367220454E+01, h = 1.579813570493616E-07 +step = 118, newt = 2, ldw = 8.322147625206496E-03, h = 1.579813570493616E-07 +step = 119, newt = 1, ldw = 1.064926772938922E+01, h = 1.579236724527747E-07 +step = 119, newt = 2, ldw = 8.429214380014318E-03, h = 1.579236724527747E-07 +step = 120, newt = 1, ldw = 1.064094484364206E+01, h = 1.578991804703025E-07 +step = 120, newt = 2, ldw = 8.494774979906316E-03, h = 1.578991804703025E-07 +step = 121, newt = 1, ldw = 1.063229850638546E+01, h = 1.578991804703025E-07 +step = 121, newt = 2, ldw = 8.533416603851235E-03, h = 1.578991804703025E-07 +step = 122, newt = 1, ldw = 1.061622377115741E+01, h = 1.578991804703025E-07 +step = 122, newt = 2, ldw = 8.548764898420214E-03, h = 1.578991804703025E-07 +step = 123, newt = 1, ldw = 1.058733535684775E+01, h = 1.578991804703025E-07 +step = 123, newt = 2, ldw = 8.542980634241464E-03, h = 1.578991804703025E-07 +step = 124, newt = 1, ldw = 1.053931245346659E+01, h = 1.578991804703025E-07 +step = 124, newt = 2, ldw = 8.515001000642279E-03, h = 1.578991804703025E-07 +step = 125, newt = 1, ldw = 1.046189490174230E+01, h = 1.578991804703025E-07 +step = 125, newt = 2, ldw = 8.459047412976673E-03, h = 1.578991804703025E-07 +step = 126, newt = 1, ldw = 1.033918810094339E+01, h = 1.578991804703025E-07 +step = 126, newt = 2, ldw = 8.363827595796031E-03, h = 1.578991804703025E-07 +step = 127, newt = 1, ldw = 1.014759934985751E+01, h = 1.578991804703025E-07 +step = 127, newt = 2, ldw = 8.211223942313842E-03, h = 1.578991804703025E-07 +step = 128, newt = 1, ldw = 9.854084459650586E+00, h = 1.578991804703025E-07 +step = 128, newt = 2, ldw = 7.975098384137515E-03, h = 1.578991804703025E-07 +step = 129, newt = 1, ldw = 9.416513172098135E+00, h = 1.578991804703025E-07 +step = 129, newt = 2, ldw = 7.621728500896537E-03, h = 1.578991804703025E-07 +step = 130, newt = 1, ldw = 8.789607823345950E+00, h = 1.578991804703025E-07 +step = 130, newt = 2, ldw = 7.114702932224733E-03, h = 1.578991804703025E-07 +step = 131, newt = 1, ldw = 7.940568120972322E+00, h = 1.578991804703025E-07 +step = 131, newt = 2, ldw = 6.427624829885826E-03, h = 1.578991804703025E-07 +step = 132, newt = 1, ldw = 6.054860484131982E+00, h = 1.578991804703025E-07 +step = 132, newt = 2, ldw = 4.901253104920733E-03, h = 1.578991804703025E-07 +step = 133, newt = 1, ldw = 3.105420586630204E+00, h = 1.578991804703025E-07 +step = 134, newt = 1, ldw = 5.398423189000604E+00, h = 2.289742877245230E-07 +step = 134, newt = 2, ldw = 6.077300864316877E-03, h = 2.289742877245230E-07 +step = 135, newt = 1, ldw = 5.413791013151093E+00, h = 2.590516448444283E-07 +step = 135, newt = 2, ldw = 1.572746647288957E-07, h = 2.590516448444283E-07 +step = 136, newt = 1, ldw = 5.160675474315381E+00, h = 3.193304711568657E-07 +step = 137, newt = 1, ldw = 6.167848836277255E+00, h = 4.326146609411660E-07 +step = 138, newt = 1, ldw = 6.126670158474519E+00, h = 5.883425741365308E-07 +step = 139, newt = 1, ldw = 5.420287321214617E+00, h = 8.849496714666515E-07 +step = 140, newt = 1, ldw = 4.259799021278498E+00, h = 1.585021904213722E-06 +step = 141, newt = 1, ldw = 2.527842174279280E+00, h = 4.023723327181765E-06 +step = 141, newt = 2, ldw = 1.336904494679520E-05, h = 4.023723327181765E-06 +step = 142, newt = 1, ldw = 8.212988122608035E-01, h = 1.774203245154785E-05 +step = 143, newt = 1, ldw = 4.671980048231489E-01, h = 1.413468175897357E-04 +step = 144, newt = 1, ldw = 7.399022604502763E-03, h = 1.130774540717885E-03 +step = 145, newt = 1, ldw = 4.148962126314440E-01, h = 9.046196325743083E-03 +step = 146, newt = 1, ldw = 4.102013415247364E+00, h = 7.236957060594466E-02 +step = 146, newt = 2, ldw = 2.840600350418560E-01, h = 7.236957060594466E-02 +step = 146, newt = 3, ldw = 2.262804904572417E-02, h = 7.236957060594466E-02 +step = 147, newt = 1, ldw = 5.458761054609476E-01, h = 1.902968025439128E-01 +step = 147, newt = 2, ldw = 1.237071740038848E-01, h = 1.902968025439128E-01 +step = 147, newt = 3, ldw = 2.532404214200263E-02, h = 1.902968025439128E-01 +step = 148, newt = 1, ldw = 3.567494822256608E+01, h = 3.270191529755309E-01 +step = 148, newt = 2, ldw = 1.897605753442047E+01, h = 3.270191529755309E-01 +step = 149, newt = 1, ldw = 6.219745790531841E+00, h = 1.799013518650076E-01 +step = 149, newt = 2, ldw = 1.519702219879884E+00, h = 1.799013518650076E-01 +step = 149, newt = 3, ldw = 4.062991321393085E-01, h = 1.799013518650076E-01 +step = 149, newt = 4, ldw = 1.088510708540817E-01, h = 1.799013518650076E-01 +step = 149, newt = 5, ldw = 2.858804994939246E-02, h = 1.799013518650076E-01 +step = 150, newt = 1, ldw = 2.751909814027291E+01, h = 1.799013518650076E-01 +step = 150, newt = 2, ldw = 1.075330005161572E+01, h = 1.799013518650076E-01 +step = 151, newt = 1, ldw = 7.463794445229401E+00, h = 1.067017522677021E-01 +step = 151, newt = 2, ldw = 1.433021795852502E+00, h = 1.067017522677021E-01 +step = 151, newt = 3, ldw = 3.232097390649537E-01, h = 1.067017522677021E-01 +step = 151, newt = 4, ldw = 7.448616810360237E-02, h = 1.067017522677021E-01 +step = 151, newt = 5, ldw = 1.686815464506370E-02, h = 1.067017522677021E-01 +step = 152, newt = 1, ldw = 1.730671787862267E+01, h = 1.067017522677021E-01 +step = 152, newt = 2, ldw = 5.507339137718899E+00, h = 1.067017522677021E-01 +step = 153, newt = 1, ldw = 7.977416570877262E+00, h = 7.933745335758620E-02 +step = 153, newt = 2, ldw = 1.701671447250841E+00, h = 7.933745335758620E-02 +step = 153, newt = 3, ldw = 4.052930755287679E-01, h = 7.933745335758620E-02 +step = 153, newt = 4, ldw = 9.730768934889115E-02, h = 7.933745335758620E-02 +step = 153, newt = 5, ldw = 2.290004173890252E-02, h = 7.933745335758620E-02 +step = 154, newt = 1, ldw = 2.625982787971731E+01, h = 7.933745335758620E-02 +step = 154, newt = 2, ldw = 9.175815055791910E+00, h = 7.933745335758620E-02 +step = 155, newt = 1, ldw = 8.856692199117470E+00, h = 5.189646321882772E-02 +step = 155, newt = 2, ldw = 1.724308281575367E+00, h = 5.189646321882772E-02 +step = 155, newt = 3, ldw = 3.822452051794467E-01, h = 5.189646321882772E-02 +step = 155, newt = 4, ldw = 8.586292399645813E-02, h = 5.189646321882772E-02 +step = 155, newt = 5, ldw = 1.891240175768895E-02, h = 5.189646321882772E-02 +step = 156, newt = 1, ldw = 2.368670817234850E+01, h = 5.189646321882772E-02 +step = 156, newt = 2, ldw = 7.657070603738724E+00, h = 5.189646321882772E-02 +step = 157, newt = 1, ldw = 9.564153522507114E+00, h = 3.663379498113982E-02 +step = 157, newt = 2, ldw = 1.911631338696802E+00, h = 3.663379498113982E-02 +step = 157, newt = 3, ldw = 4.262456044581129E-01, h = 3.663379498113982E-02 +step = 157, newt = 4, ldw = 9.581305110490419E-02, h = 3.663379498113982E-02 +step = 157, newt = 5, ldw = 2.109505861229110E-02, h = 3.663379498113982E-02 +step = 158, newt = 1, ldw = 2.832480902456543E+01, h = 3.649602305099861E-02 +step = 158, newt = 2, ldw = 9.354160244737987E+00, h = 3.649602305099861E-02 +step = 159, newt = 1, ldw = 1.037433081667180E+01, h = 2.476047812189647E-02 +step = 159, newt = 2, ldw = 1.993389632327963E+00, h = 2.476047812189647E-02 +step = 159, newt = 3, ldw = 4.290393369631955E-01, h = 2.476047812189647E-02 +step = 159, newt = 4, ldw = 9.318979187065389E-02, h = 2.476047812189647E-02 +step = 159, newt = 5, ldw = 1.982223196420317E-02, h = 2.476047812189647E-02 +step = 160, newt = 1, ldw = 2.885112054118565E+01, h = 2.476047812189647E-02 +step = 160, newt = 2, ldw = 9.276160500979799E+00, h = 2.476047812189647E-02 +step = 161, newt = 1, ldw = 1.103614532102126E+01, h = 1.712774187515987E-02 +step = 161, newt = 2, ldw = 2.116529258696050E+00, h = 1.712774187515987E-02 +step = 161, newt = 3, ldw = 4.507258000362395E-01, h = 1.712774187515987E-02 +step = 161, newt = 4, ldw = 9.666073228172745E-02, h = 1.712774187515987E-02 +step = 161, newt = 5, ldw = 2.028815372312474E-02, h = 1.712774187515987E-02 +step = 162, newt = 1, ldw = 2.927605496018488E+01, h = 1.667952294599732E-02 +step = 162, newt = 2, ldw = 9.063766757331475E+00, h = 1.667952294599732E-02 +step = 163, newt = 1, ldw = 1.209490973331524E+01, h = 1.187363287866596E-02 +step = 163, newt = 2, ldw = 2.318657524037120E+00, h = 1.187363287866596E-02 +step = 163, newt = 3, ldw = 4.916337335471574E-01, h = 1.187363287866596E-02 +step = 163, newt = 4, ldw = 1.048547404486513E-01, h = 1.187363287866596E-02 +step = 163, newt = 5, ldw = 2.187532569302820E-02, h = 1.187363287866596E-02 +step = 164, newt = 1, ldw = 2.996419968071447E+01, h = 1.123504929388904E-02 +step = 164, newt = 2, ldw = 8.980116667181656E+00, h = 1.123504929388904E-02 +step = 165, newt = 1, ldw = 1.319816349721159E+01, h = 8.186050303701670E-03 +step = 165, newt = 2, ldw = 2.524106591997509E+00, h = 8.186050303701670E-03 +step = 165, newt = 3, ldw = 5.328291377364609E-01, h = 8.186050303701670E-03 +step = 165, newt = 4, ldw = 1.130503923726351E-01, h = 8.186050303701670E-03 +step = 165, newt = 5, ldw = 2.345102685036251E-02, h = 8.186050303701670E-03 +step = 166, newt = 1, ldw = 3.042023372934433E+01, h = 7.541411706855082E-03 +step = 166, newt = 2, ldw = 8.833436188310809E+00, h = 7.541411706855082E-03 +step = 167, newt = 1, ldw = 1.427782062666545E+01, h = 5.625142316775764E-03 +step = 167, newt = 2, ldw = 2.729225936953076E+00, h = 5.625142316775764E-03 +step = 167, newt = 3, ldw = 5.746034201790312E-01, h = 5.625142316775764E-03 +step = 167, newt = 4, ldw = 1.214853388823014E-01, h = 5.625142316775764E-03 +step = 167, newt = 5, ldw = 2.509901710938268E-02, h = 5.625142316775764E-03 +step = 168, newt = 1, ldw = 3.071896219290578E+01, h = 5.045345416388469E-03 +step = 168, newt = 2, ldw = 8.648357690759763E+00, h = 5.045345416388469E-03 +step = 169, newt = 1, ldw = 1.536045806466094E+01, h = 3.852955919595468E-03 +step = 169, newt = 2, ldw = 2.938266750742225E+00, h = 3.852955919595468E-03 +step = 169, newt = 3, ldw = 6.178864865794782E-01, h = 3.852955919595468E-03 +step = 169, newt = 4, ldw = 1.303607737139108E-01, h = 3.852955919595468E-03 +step = 169, newt = 5, ldw = 2.685957860230218E-02, h = 3.852955919595468E-03 +step = 170, newt = 1, ldw = 3.085930854171281E+01, h = 3.363431024694772E-03 +step = 170, newt = 2, ldw = 8.423455761779095E+00, h = 3.363431024694772E-03 +step = 171, newt = 1, ldw = 1.645330162733004E+01, h = 2.631170793077139E-03 +step = 171, newt = 2, ldw = 3.153152782976279E+00, h = 2.631170793077139E-03 +step = 171, newt = 3, ldw = 6.631210593774037E-01, h = 2.631170793077139E-03 +step = 171, newt = 4, ldw = 1.397550512703242E-01, h = 2.631170793077139E-03 +step = 171, newt = 5, ldw = 2.874190216789492E-02, h = 2.631170793077139E-03 +step = 172, newt = 1, ldw = 3.075063647140002E+01, h = 2.230917916492913E-03 +step = 172, newt = 2, ldw = 8.117652139804170E+00, h = 2.230917916492913E-03 +step = 172, newt = 3, ldw = 2.247699044315456E+00, h = 2.230917916492913E-03 +step = 173, newt = 1, ldw = 1.672548421167645E+01, h = 1.755776277038168E-03 +step = 173, newt = 2, ldw = 3.121606644816270E+00, h = 1.755776277038168E-03 +step = 173, newt = 3, ldw = 6.408076281752678E-01, h = 1.755776277038168E-03 +step = 173, newt = 4, ldw = 1.317911854128606E-01, h = 1.755776277038168E-03 +step = 173, newt = 5, ldw = 2.642773247201057E-02, h = 1.755776277038168E-03 +step = 174, newt = 1, ldw = 2.933805145418025E+01, h = 1.480085696025712E-03 +step = 174, newt = 2, ldw = 7.462222123180601E+00, h = 1.480085696025712E-03 +step = 174, newt = 3, ldw = 1.987525547119485E+00, h = 1.480085696025712E-03 +step = 174, newt = 4, ldw = 5.221991953341225E-01, h = 1.480085696025712E-03 +step = 174, newt = 5, ldw = 1.331466487878366E-01, h = 1.480085696025712E-03 +step = 174, newt = 6, ldw = 3.314143219147383E-02, h = 1.480085696025712E-03 +step = 175, newt = 1, ldw = 4.150094963044543E+01, h = 9.053631116368244E-04 +step = 175, newt = 2, ldw = 9.931287835152297E+00, h = 9.053631116368244E-04 +step = 175, newt = 3, ldw = 2.637587763685524E+00, h = 9.053631116368244E-04 +step = 175, newt = 4, ldw = 6.948921121509704E-01, h = 9.053631116368244E-04 +step = 176, newt = 1, ldw = 2.332233714202069E+01, h = 7.034582774752608E-04 +step = 176, newt = 2, ldw = 3.819470016022847E+00, h = 7.034582774752608E-04 +step = 176, newt = 3, ldw = 7.314621959768310E-01, h = 7.034582774752608E-04 +step = 176, newt = 4, ldw = 1.414383797057878E-01, h = 7.034582774752608E-04 +step = 176, newt = 5, ldw = 2.653830101559703E-02, h = 7.034582774752608E-04 +step = 177, newt = 1, ldw = 1.665399739856992E+01, h = 4.887438785107445E-04 +step = 177, newt = 2, ldw = 2.940719051204553E+00, h = 4.887438785107445E-04 +step = 177, newt = 3, ldw = 5.599104309724650E-01, h = 4.887438785107445E-04 +step = 177, newt = 4, ldw = 1.051173791261706E-01, h = 4.887438785107445E-04 +step = 177, newt = 5, ldw = 1.901919300938409E-02, h = 4.887438785107445E-04 +step = 178, newt = 1, ldw = 2.460170209909534E+01, h = 3.899683566919322E-04 +step = 178, newt = 2, ldw = 5.270776715972536E+00, h = 3.899683566919322E-04 +step = 178, newt = 3, ldw = 1.168172516497118E+00, h = 3.899683566919322E-04 +step = 178, newt = 4, ldw = 2.505982678513354E-01, h = 3.899683566919322E-04 +step = 178, newt = 5, ldw = 5.130393091954531E-02, h = 3.899683566919322E-04 +step = 179, newt = 1, ldw = 2.721118780980889E+01, h = 2.436344628871809E-04 +step = 179, newt = 2, ldw = 5.027842435336031E+00, h = 2.436344628871809E-04 +step = 179, newt = 3, ldw = 9.820906323912296E-01, h = 2.436344628871809E-04 +step = 179, newt = 4, ldw = 1.822335992955270E-01, h = 2.436344628871809E-04 +step = 179, newt = 5, ldw = 3.157645534603416E-02, h = 2.436344628871809E-04 +step = 180, newt = 1, ldw = 1.503673142247091E+01, h = 1.329013743339831E-04 +step = 180, newt = 2, ldw = 1.809175036472548E+00, h = 1.329013743339831E-04 +step = 180, newt = 3, ldw = 2.238245088219546E-01, h = 1.329013743339831E-04 +step = 180, newt = 4, ldw = 2.510189399605737E-02, h = 1.329013743339831E-04 +step = 181, newt = 1, ldw = 7.853109447137832E+00, h = 8.661682077803249E-05 +step = 181, newt = 2, ldw = 6.886618315307691E-01, h = 8.661682077803249E-05 +step = 181, newt = 3, ldw = 5.628606735734951E-02, h = 8.661682077803249E-05 +step = 182, newt = 1, ldw = 7.165750249067095E+00, h = 6.895245888575511E-05 +step = 182, newt = 2, ldw = 5.733676799515314E-01, h = 6.895245888575511E-05 +step = 182, newt = 3, ldw = 3.924565775669845E-02, h = 6.895245888575511E-05 +step = 183, newt = 1, ldw = 7.055288976952602E+00, h = 5.306185534692399E-05 +step = 183, newt = 2, ldw = 4.582938841618340E-01, h = 5.306185534692399E-05 +step = 183, newt = 3, ldw = 2.431174484583507E-02, h = 5.306185534692399E-05 +step = 184, newt = 1, ldw = 6.103854188573081E+00, h = 4.011322993639330E-05 +step = 184, newt = 2, ldw = 3.004483266885171E-01, h = 4.011322993639330E-05 +step = 184, newt = 3, ldw = 1.159273048365065E-02, h = 4.011322993639330E-05 +step = 185, newt = 1, ldw = 5.558117411811423E+00, h = 3.180464689850865E-05 +step = 185, newt = 2, ldw = 2.214788435626123E-01, h = 3.180464689850865E-05 +step = 186, newt = 1, ldw = 5.579530275350298E+00, h = 2.608246243454473E-05 +step = 186, newt = 2, ldw = 1.891476661097477E-01, h = 2.608246243454473E-05 +step = 187, newt = 1, ldw = 5.472084453301593E+00, h = 2.140578538828773E-05 +step = 187, newt = 2, ldw = 1.580251014252577E-01, h = 2.140578538828773E-05 +step = 188, newt = 1, ldw = 5.368950024650862E+00, h = 1.778693283405212E-05 +step = 188, newt = 2, ldw = 1.351780217899979E-01, h = 1.778693283405212E-05 +step = 189, newt = 1, ldw = 5.325564685276029E+00, h = 1.494867990262395E-05 +step = 189, newt = 2, ldw = 1.196468548371897E-01, h = 1.494867990262395E-05 +step = 190, newt = 1, ldw = 5.305550740042687E+00, h = 1.266177437998391E-05 +step = 190, newt = 2, ldw = 1.082362456762791E-01, h = 1.266177437998391E-05 +step = 191, newt = 1, ldw = 5.294640901532725E+00, h = 1.079564130017312E-05 +step = 191, newt = 2, ldw = 9.963397190983510E-02, h = 1.079564130017312E-05 +step = 192, newt = 1, ldw = 5.290905004055221E+00, h = 9.256712963951848E-06 +step = 192, newt = 2, ldw = 9.313285616874659E-02, h = 9.256712963951848E-06 +step = 193, newt = 1, ldw = 5.291699214560380E+00, h = 7.974478536733299E-06 +step = 193, newt = 2, ldw = 8.817757465511528E-02, h = 7.974478536733299E-06 +step = 194, newt = 1, ldw = 5.294912489285251E+00, h = 6.896632989380138E-06 +step = 194, newt = 2, ldw = 8.437076474570630E-02, h = 6.896632989380138E-06 +step = 195, newt = 1, ldw = 5.299315082080466E+00, h = 5.983732036992428E-06 +step = 195, newt = 2, ldw = 8.142947373321423E-02, h = 5.983732036992428E-06 +step = 196, newt = 1, ldw = 5.304142666764546E+00, h = 5.205538365023134E-06 +step = 196, newt = 2, ldw = 7.914552600208975E-02, h = 5.205538365023134E-06 +step = 197, newt = 1, ldw = 5.308925929621296E+00, h = 4.538563083401414E-06 +step = 197, newt = 2, ldw = 7.736337060354447E-02, h = 4.538563083401414E-06 +step = 198, newt = 1, ldw = 5.313398656164651E+00, h = 3.964316718579926E-06 +step = 198, newt = 2, ldw = 7.596539806885710E-02, h = 3.964316718579926E-06 +step = 199, newt = 1, ldw = 5.317427831732149E+00, h = 3.468056188630952E-06 +step = 199, newt = 2, ldw = 7.486169638853045E-02, h = 3.468056188630952E-06 +step = 200, newt = 1, ldw = 5.320965458272008E+00, h = 3.037879788004230E-06 +step = 200, newt = 2, ldw = 7.398285433139011E-02, h = 3.037879788004230E-06 +step = 201, newt = 1, ldw = 5.324015676434003E+00, h = 2.664067262414495E-06 +step = 201, newt = 2, ldw = 7.327478316927991E-02, h = 2.664067262414495E-06 +step = 202, newt = 1, ldw = 5.326612368150274E+00, h = 2.338594347453750E-06 +step = 202, newt = 2, ldw = 7.269490252013049E-02, h = 2.338594347453750E-06 +step = 203, newt = 1, ldw = 5.328804149218738E+00, h = 2.054772408898708E-06 +step = 203, newt = 2, ldw = 7.220926525410715E-02, h = 2.054772408898708E-06 +step = 204, newt = 1, ldw = 5.330644494236979E+00, h = 1.806978365533046E-06 +step = 204, newt = 2, ldw = 7.179033258229946E-02, h = 1.806978365533046E-06 +step = 205, newt = 1, ldw = 5.332185302723570E+00, h = 1.590450192365771E-06 +step = 205, newt = 2, ldw = 7.141519384165625E-02, h = 1.590450192365771E-06 +step = 206, newt = 1, ldw = 5.333472592819621E+00, h = 1.401130413117843E-06 +step = 206, newt = 2, ldw = 7.106407346039439E-02, h = 1.401130413117843E-06 +step = 207, newt = 1, ldw = 5.334543222285681E+00, h = 1.235545035893640E-06 +step = 207, newt = 2, ldw = 7.071898915299669E-02, h = 1.235545035893640E-06 +step = 208, newt = 1, ldw = 5.335421568764674E+00, h = 1.090709000068160E-06 +step = 208, newt = 2, ldw = 7.036242218394093E-02, h = 1.090709000068160E-06 +step = 209, newt = 1, ldw = 5.336114869236820E+00, h = 9.640518323698546E-07 +step = 209, newt = 2, ldw = 6.997582609561637E-02, h = 9.640518323698546E-07 +step = 210, newt = 1, ldw = 5.336605210247750E+00, h = 8.533591903472576E-07 +step = 210, newt = 2, ldw = 6.953771424524932E-02, h = 8.533591903472576E-07 +step = 211, newt = 1, ldw = 5.336834455755541E+00, h = 7.567275862125266E-07 +step = 211, newt = 2, ldw = 6.902087987759457E-02, h = 7.567275862125266E-07 +step = 212, newt = 1, ldw = 5.336674383337045E+00, h = 6.725311446731572E-07 +step = 212, newt = 2, ldw = 6.838789666624878E-02, h = 6.725311446731572E-07 +step = 213, newt = 1, ldw = 5.335864420697332E+00, h = 5.994012396291315E-07 +step = 213, newt = 2, ldw = 6.758311936954699E-02, h = 5.994012396291315E-07 +step = 214, newt = 1, ldw = 5.333873164796207E+00, h = 5.362233095430004E-07 +step = 214, newt = 2, ldw = 6.651710683236287E-02, h = 5.362233095430004E-07 +step = 215, newt = 1, ldw = 5.329562898120789E+00, h = 4.821627900410602E-07 +step = 215, newt = 2, ldw = 6.503308002688100E-02, h = 4.821627900410602E-07 +step = 216, newt = 1, ldw = 5.320278279895900E+00, h = 4.367523472118046E-07 +step = 216, newt = 2, ldw = 6.282518269956379E-02, h = 4.367523472118046E-07 +step = 217, newt = 1, ldw = 5.298947582327378E+00, h = 4.001363771919698E-07 +step = 217, newt = 2, ldw = 5.920343189754624E-02, h = 4.001363771919698E-07 +step = 218, newt = 1, ldw = 5.242468918733432E+00, h = 3.738179974499161E-07 +step = 218, newt = 2, ldw = 5.223411554264052E-02, h = 3.738179974499161E-07 +step = 219, newt = 1, ldw = 4.894736404623235E+00, h = 3.601087749256357E-07 +step = 219, newt = 2, ldw = 3.259220398999733E-02, h = 3.601087749256357E-07 +step = 220, newt = 1, ldw = 2.995278407310172E+00, h = 3.674694136655150E-07 +step = 220, newt = 2, ldw = 4.072160009035332E-02, h = 3.674694136655150E-07 +step = 221, newt = 1, ldw = 2.432492733471978E+01, h = 4.649135613222773E-07 +step = 221, newt = 2, ldw = 1.593482301604156E-01, h = 4.649135613222773E-07 +step = 222, newt = 1, ldw = 1.753956369588941E+00, h = 2.304654264903980E-07 +step = 222, newt = 2, ldw = 8.488811869624911E-03, h = 2.304654264903980E-07 +step = 223, newt = 1, ldw = 1.505139242739700E+00, h = 1.705078790614448E-07 +step = 223, newt = 2, ldw = 3.727685673819770E-03, h = 1.705078790614448E-07 +step = 224, newt = 1, ldw = 3.905926183716427E+00, h = 2.087996869540696E-07 +step = 224, newt = 2, ldw = 4.759055603847967E-02, h = 2.087996869540696E-07 +step = 225, newt = 1, ldw = 5.147945289371666E+00, h = 2.002248216759833E-07 +step = 225, newt = 2, ldw = 1.189666823738310E-01, h = 2.002248216759833E-07 +step = 226, newt = 1, ldw = 3.793651475931946E+00, h = 2.126980999889548E-07 +step = 226, newt = 2, ldw = 1.953845308744101E-01, h = 2.126980999889548E-07 +step = 227, newt = 1, ldw = 1.582019907046664E+01, h = 2.701503459597869E-07 +step = 227, newt = 2, ldw = 6.672556693955098E-01, h = 2.701503459597869E-07 +step = 227, newt = 3, ldw = 2.829735302043523E-02, h = 2.701503459597869E-07 +step = 228, newt = 1, ldw = 2.771457712849480E+00, h = 1.656485554927038E-07 +step = 228, newt = 2, ldw = 3.032906140701765E-02, h = 1.656485554927038E-07 +step = 229, newt = 1, ldw = 2.690005292570111E+00, h = 1.302689426601023E-07 +step = 229, newt = 2, ldw = 3.580091927156584E-02, h = 1.302689426601023E-07 +step = 230, newt = 1, ldw = 5.238311914891502E+00, h = 1.709765176461099E-07 +step = 230, newt = 2, ldw = 1.202633589351685E-01, h = 1.709765176461099E-07 +step = 231, newt = 1, ldw = 3.162958954527383E+00, h = 1.964074579506422E-07 +step = 231, newt = 2, ldw = 2.463525301431348E-02, h = 1.964074579506422E-07 +step = 232, newt = 1, ldw = 1.456987722270496E+01, h = 2.145308169339812E-07 +step = 232, newt = 2, ldw = 5.254776777844574E-01, h = 2.145308169339812E-07 +step = 232, newt = 3, ldw = 1.079347736375193E-02, h = 2.145308169339812E-07 +step = 233, newt = 1, ldw = 5.949225573550574E+00, h = 1.622451190226579E-07 +step = 233, newt = 2, ldw = 1.321518405078417E-01, h = 1.622451190226579E-07 +step = 234, newt = 1, ldw = 5.549630667140137E+00, h = 1.438403890735037E-07 +step = 234, newt = 2, ldw = 5.921654156466909E-02, h = 1.438403890735037E-07 +step = 235, newt = 1, ldw = 7.032224589911532E+00, h = 1.692930978368381E-07 +step = 235, newt = 2, ldw = 5.842724304619878E-02, h = 1.692930978368381E-07 +step = 236, newt = 1, ldw = 6.095912761380398E+00, h = 1.918152560875219E-07 +step = 236, newt = 2, ldw = 1.014530374539966E-02, h = 1.918152560875219E-07 +step = 237, newt = 1, ldw = 1.492300661722487E+01, h = 3.174043510756384E-07 +step = 237, newt = 2, ldw = 6.086909188283154E-01, h = 3.174043510756384E-07 +step = 237, newt = 3, ldw = 4.092160356125470E-03, h = 3.174043510756384E-07 +step = 238, newt = 1, ldw = 2.735282122189810E+00, h = 2.041285168761089E-07 +step = 238, newt = 2, ldw = 6.988517281215881E-02, h = 2.041285168761089E-07 +step = 239, newt = 1, ldw = 5.783968517058812E+00, h = 1.548630765784411E-07 +step = 239, newt = 2, ldw = 2.247399221076707E-02, h = 1.548630765784411E-07 +step = 240, newt = 1, ldw = 7.149034562501154E+00, h = 1.619128275836987E-07 +step = 240, newt = 2, ldw = 1.688030486622602E-02, h = 1.619128275836987E-07 +step = 241, newt = 1, ldw = 1.071362075481936E+01, h = 1.712368661961728E-07 +step = 241, newt = 2, ldw = 1.638127124350602E-02, h = 1.712368661961728E-07 +step = 242, newt = 1, ldw = 1.172065609552840E+01, h = 1.666332559106681E-07 +step = 242, newt = 2, ldw = 9.981834960415643E-03, h = 1.666332559106681E-07 +step = 243, newt = 1, ldw = 1.136143199628798E+01, h = 1.630437719297974E-07 +step = 243, newt = 2, ldw = 1.506462416686512E-02, h = 1.630437719297974E-07 +step = 244, newt = 1, ldw = 1.104958929077749E+01, h = 1.609021565913567E-07 +step = 244, newt = 2, ldw = 3.240538968390202E-03, h = 1.609021565913567E-07 +step = 245, newt = 1, ldw = 1.088589596056910E+01, h = 1.597042602686863E-07 +step = 245, newt = 2, ldw = 5.142332820833670E-03, h = 1.597042602686863E-07 +step = 246, newt = 1, ldw = 1.079050776343196E+01, h = 1.589635992876782E-07 +step = 246, newt = 2, ldw = 6.308172554464114E-03, h = 1.589635992876782E-07 +step = 247, newt = 1, ldw = 1.073155608054700E+01, h = 1.585130973290163E-07 +step = 247, newt = 2, ldw = 7.017611642540667E-03, h = 1.585130973290163E-07 +step = 248, newt = 1, ldw = 1.069553602386160E+01, h = 1.582387032235062E-07 +step = 248, newt = 2, ldw = 7.452715446526004E-03, h = 1.582387032235062E-07 +step = 249, newt = 1, ldw = 1.067308162662458E+01, h = 1.580724192927071E-07 +step = 249, newt = 2, ldw = 7.720898279733287E-03, h = 1.580724192927071E-07 +step = 250, newt = 1, ldw = 1.065853261886071E+01, h = 1.579741511271512E-07 +step = 250, newt = 2, ldw = 7.886542156255075E-03, h = 1.579741511271512E-07 +step = 251, newt = 1, ldw = 1.064835721463648E+01, h = 1.579206391545427E-07 +step = 251, newt = 2, ldw = 7.988710458546655E-03, h = 1.579206391545427E-07 +step = 252, newt = 1, ldw = 1.064015356998269E+01, h = 1.578993444024153E-07 +step = 252, newt = 2, ldw = 8.051236468979804E-03, h = 1.578993444024153E-07 +step = 253, newt = 1, ldw = 1.063082921520760E+01, h = 1.578993444024153E-07 +step = 253, newt = 2, ldw = 8.087437851735622E-03, h = 1.578993444024153E-07 +step = 254, newt = 1, ldw = 1.061355414366396E+01, h = 1.578993444024153E-07 +step = 254, newt = 2, ldw = 8.101120974068614E-03, h = 1.578993444024153E-07 +step = 255, newt = 1, ldw = 1.058291866826167E+01, h = 1.578993444024153E-07 +step = 255, newt = 2, ldw = 8.094328468486385E-03, h = 1.578993444024153E-07 +step = 256, newt = 1, ldw = 1.053223334971544E+01, h = 1.578993444024153E-07 +step = 256, newt = 2, ldw = 8.065779637272345E-03, h = 1.578993444024153E-07 +step = 257, newt = 1, ldw = 1.045070677325733E+01, h = 1.578993444024153E-07 +step = 257, newt = 2, ldw = 8.009595263941200E-03, h = 1.578993444024153E-07 +step = 258, newt = 1, ldw = 1.032169669863054E+01, h = 1.578993444024153E-07 +step = 258, newt = 2, ldw = 7.914501541751407E-03, h = 1.578993444024153E-07 +step = 259, newt = 1, ldw = 1.012063265341646E+01, h = 1.578993444024153E-07 +step = 259, newt = 2, ldw = 7.762577166041072E-03, h = 1.578993444024153E-07 +step = 260, newt = 1, ldw = 9.813380773596240E+00, h = 1.578993444024153E-07 +step = 260, newt = 2, ldw = 7.528211798903035E-03, h = 1.578993444024153E-07 +step = 261, newt = 1, ldw = 9.357025849687686E+00, h = 1.578993444024153E-07 +step = 261, newt = 2, ldw = 7.178839539882020E-03, h = 1.578993444024153E-07 +step = 262, newt = 1, ldw = 8.706697266852826E+00, h = 1.578993444024153E-07 +step = 262, newt = 2, ldw = 6.680260266785349E-03, h = 1.578993444024153E-07 +step = 263, newt = 1, ldw = 7.832410430073477E+00, h = 1.578993444024153E-07 +step = 263, newt = 2, ldw = 6.009614623005186E-03, h = 1.578993444024153E-07 +step = 264, newt = 1, ldw = 5.583379394880296E+00, h = 1.578993444024153E-07 +step = 264, newt = 2, ldw = 4.284024340781067E-03, h = 1.578993444024153E-07 +step = 265, newt = 1, ldw = 2.903452473236002E+00, h = 1.578993444024153E-07 +step = 266, newt = 1, ldw = 5.361325898532159E+00, h = 2.328670981838744E-07 +step = 266, newt = 2, ldw = 5.800760308919221E-03, h = 2.328670981838744E-07 +step = 267, newt = 1, ldw = 5.429411663451372E+00, h = 2.636257663418310E-07 +step = 267, newt = 2, ldw = 1.664896217491824E-07, h = 2.636257663418310E-07 +step = 268, newt = 1, ldw = 5.158004272609451E+00, h = 3.257419560439063E-07 +step = 269, newt = 1, ldw = 6.147391581017748E+00, h = 4.430046613478243E-07 +step = 270, newt = 1, ldw = 6.082321880318282E+00, h = 6.063675553135037E-07 +step = 271, newt = 1, ldw = 5.342432771284725E+00, h = 9.222214653786987E-07 +step = 272, newt = 1, ldw = 4.133449508626873E+00, h = 1.686535931689226E-06 +step = 273, newt = 1, ldw = 2.366057043992620E+00, h = 4.471378619617967E-06 +step = 273, newt = 2, ldw = 1.380863704471743E-05, h = 4.471378619617967E-06 +step = 274, newt = 1, ldw = 7.630946798791799E-01, h = 2.064344427918861E-05 +step = 275, newt = 1, ldw = 3.891184186327734E-01, h = 1.651475542335088E-04 +step = 276, newt = 1, ldw = 1.848326036372911E-02, h = 1.321180433868071E-03 +step = 277, newt = 1, ldw = 4.277275517509970E-01, h = 1.056944347094457E-02 +step = 278, newt = 1, ldw = 4.756393588652925E+00, h = 8.455554776755653E-02 +step = 278, newt = 2, ldw = 3.849872620986047E-01, h = 8.455554776755653E-02 +step = 278, newt = 3, ldw = 3.585805647489550E-02, h = 8.455554776755653E-02 +step = 279, newt = 1, ldw = 1.386212687660640E+00, h = 2.132444932192438E-01 +step = 279, newt = 2, ldw = 3.537056545450799E-01, h = 2.132444932192438E-01 +step = 279, newt = 3, ldw = 8.537535547460637E-02, h = 2.132444932192438E-01 +step = 279, newt = 4, ldw = 2.024952402753639E-02, h = 2.132444932192438E-01 +step = 280, newt = 1, ldw = 1.375185222984066E+00, h = 7.582774801688652E-02 +step = 280, newt = 2, ldw = 1.133910759108643E-01, h = 7.582774801688652E-02 +Radau5: Radau method (Radau IIA) (implicit, order 5, embedded) +Number of function evaluations = 2249 +Number of Jacobian evaluations = 162 +Number of factorizations = 253 +Number of lin sys solutions = 668 +Number of performed steps = 280 +Number of accepted steps = 242 +Number of rejected steps = 8 +Number of iterations (maximum) = 6 +y = 1.706163410178069E+00 -8.927971289309896E-01 +h = 1.510987114560156E-01 diff --git a/russell_ode/rustfmt.toml b/russell_ode/rustfmt.toml new file mode 100644 index 00000000..866c7561 --- /dev/null +++ b/russell_ode/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 \ No newline at end of file diff --git a/russell_ode/src/benchmark.rs b/russell_ode/src/benchmark.rs new file mode 100644 index 00000000..8ae0c83b --- /dev/null +++ b/russell_ode/src/benchmark.rs @@ -0,0 +1,325 @@ +use crate::Method; +use russell_lab::{format_nanoseconds, Stopwatch}; +use std::fmt::{self, Write}; + +/// Holds some statistics and benchmarking data +#[derive(Clone, Copy, Debug)] +pub struct Benchmark { + /// Holds the method + method: Method, + + /// Number of calls to ODE system function + pub n_function: usize, + + /// Number of Jacobian matrix evaluations + pub n_jacobian: usize, + + /// Number of factorizations + pub n_factor: usize, + + /// Number of linear system solutions + pub n_lin_sol: usize, + + /// Collects the number of steps, successful or not + pub n_steps: usize, + + /// Collects the number of accepted steps + pub n_accepted: usize, + + /// Collects the number of rejected steps + pub n_rejected: usize, + + /// Last number of iterations + pub n_iterations: usize, + + /// Max number of iterations among all steps + pub n_iterations_max: usize, + + /// Last accepted/suggested step size h_new + pub h_accepted: f64, + + /// Max nanoseconds spent on steps + pub nanos_step_max: u128, + + /// Max nanoseconds spent on Jacobian evaluation + pub nanos_jacobian_max: u128, + + /// Max nanoseconds spent on the coefficient matrix factorization + pub nanos_factor_max: u128, + + /// Max nanoseconds spent on the solution of the linear system + pub nanos_lin_sol_max: u128, + + /// Total nanoseconds spent on the solution + pub nanos_total: u128, + + /// Holds a stopwatch for measuring the elapsed time during a step + pub(crate) sw_step: Stopwatch, + + /// Holds a stopwatch for measuring the elapsed time during the Jacobian computation + pub(crate) sw_jacobian: Stopwatch, + + /// Holds a stopwatch for measuring the elapsed time during the coefficient matrix factorization + pub(crate) sw_factor: Stopwatch, + + /// Holds a stopwatch for measuring the elapsed time during the solution of the linear system + pub(crate) sw_lin_sol: Stopwatch, + + /// Holds a stopwatch for measuring the total elapsed time + pub(crate) sw_total: Stopwatch, +} + +impl Benchmark { + /// Allocates a new instance + pub fn new(method: Method) -> Self { + Benchmark { + method, + n_function: 0, + n_jacobian: 0, + n_factor: 0, + n_lin_sol: 0, + n_steps: 0, + n_accepted: 0, + n_rejected: 0, + n_iterations: 0, + n_iterations_max: 0, + h_accepted: 0.0, + nanos_step_max: 0, + nanos_jacobian_max: 0, + nanos_factor_max: 0, + nanos_lin_sol_max: 0, + nanos_total: 0, + sw_step: Stopwatch::new(), + sw_jacobian: Stopwatch::new(), + sw_factor: Stopwatch::new(), + sw_lin_sol: Stopwatch::new(), + sw_total: Stopwatch::new(), + } + } + + /// Resets all values + pub(crate) fn reset(&mut self, h: f64) { + self.n_function = 0; + self.n_jacobian = 0; + self.n_factor = 0; + self.n_lin_sol = 0; + self.n_steps = 0; + self.n_accepted = 0; + self.n_rejected = 0; + self.n_iterations = 0; + self.n_iterations_max = 0; + self.h_accepted = h; + self.nanos_step_max = 0; + self.nanos_jacobian_max = 0; + self.nanos_factor_max = 0; + self.nanos_lin_sol_max = 0; + self.nanos_total = 0; + } + + pub(crate) fn stop_sw_step(&mut self) { + let nanos = self.sw_step.stop(); + if nanos > self.nanos_step_max { + self.nanos_step_max = nanos; + } + } + + pub(crate) fn stop_sw_jacobian(&mut self) { + let nanos = self.sw_jacobian.stop(); + if nanos > self.nanos_jacobian_max { + self.nanos_jacobian_max = nanos; + } + } + + pub(crate) fn stop_sw_factor(&mut self) { + let nanos = self.sw_factor.stop(); + if nanos > self.nanos_factor_max { + self.nanos_factor_max = nanos; + } + } + + pub(crate) fn stop_sw_lin_sol(&mut self) { + let nanos = self.sw_lin_sol.stop(); + if nanos > self.nanos_lin_sol_max { + self.nanos_lin_sol_max = nanos; + } + } + + pub(crate) fn stop_sw_total(&mut self) { + let nanos = self.sw_total.stop(); + self.nanos_total = nanos; + } + + pub(crate) fn update_n_iterations_max(&mut self) { + if self.n_iterations > self.n_iterations_max { + self.n_iterations_max = self.n_iterations; + } + } + + pub fn summary(&self) -> String { + let mut buffer = String::new(); + if self.method.information().implicit { + write!( + &mut buffer, + "{:?}: {}\n\ + Number of function evaluations = {}\n\ + Number of Jacobian evaluations = {}\n\ + Number of factorizations = {}\n\ + Number of lin sys solutions = {}\n\ + Number of performed steps = {}\n\ + Number of accepted steps = {}\n\ + Number of rejected steps = {}\n\ + Number of iterations (maximum) = {}", + self.method, + self.method.description(), + self.n_function, + self.n_jacobian, + self.n_factor, + self.n_lin_sol, + self.n_steps, + self.n_accepted, + self.n_rejected, + self.n_iterations_max, + ) + .unwrap(); + } else { + write!( + &mut buffer, + "{:?}: {}\n\ + Number of function evaluations = {}\n\ + Number of performed steps = {}\n\ + Number of accepted steps = {}\n\ + Number of rejected steps = {}", + self.method, + self.method.description(), + self.n_function, + self.n_steps, + self.n_accepted, + self.n_rejected, + ) + .unwrap(); + } + buffer + } +} + +impl fmt::Display for Benchmark { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.method.information().implicit { + write!( + f, + "{}\n\ + Number of iterations (last step) = {}\n\ + Last accepted/suggested stepsize = {}\n\ + Max time spent on a step = {}\n\ + Max time spent on the Jacobian = {}\n\ + Max time spent on factorization = {}\n\ + Max time spent on lin solution = {}\n\ + Total time = {}", + self.summary(), + self.n_iterations, + self.h_accepted, + format_nanoseconds(self.nanos_step_max), + format_nanoseconds(self.nanos_jacobian_max), + format_nanoseconds(self.nanos_factor_max), + format_nanoseconds(self.nanos_lin_sol_max), + format_nanoseconds(self.nanos_total), + ) + .unwrap(); + } else { + write!( + f, + "{}\n\ + Last accepted/suggested stepsize = {}\n\ + Max time spent on a step = {}\n\ + Total time = {}", + self.summary(), + self.h_accepted, + format_nanoseconds(self.nanos_step_max), + format_nanoseconds(self.nanos_total), + ) + .unwrap(); + } + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::Benchmark; + use crate::Method; + + #[test] + fn clone_copy_and_debug_work() { + let mut bench = Benchmark::new(Method::Radau5); + bench.n_accepted += 1; + let copy = bench; + let clone = bench.clone(); + assert_eq!(copy.n_accepted, bench.n_accepted); + assert_eq!(clone.n_accepted, bench.n_accepted); + assert!(format!("{:?}", bench).len() > 0); + } + + #[test] + fn summary_works() { + let bench = Benchmark::new(Method::Radau5); + assert_eq!( + format!("{}", bench.summary()), + "Radau5: Radau method (Radau IIA) (implicit, order 5, embedded)\n\ + Number of function evaluations = 0\n\ + Number of Jacobian evaluations = 0\n\ + Number of factorizations = 0\n\ + Number of lin sys solutions = 0\n\ + Number of performed steps = 0\n\ + Number of accepted steps = 0\n\ + Number of rejected steps = 0\n\ + Number of iterations (maximum) = 0" + ); + let bench = Benchmark::new(Method::FwEuler); + assert_eq!( + format!("{}", bench.summary()), + "FwEuler: Forward Euler method (explicit, order 1)\n\ + Number of function evaluations = 0\n\ + Number of performed steps = 0\n\ + Number of accepted steps = 0\n\ + Number of rejected steps = 0" + ); + } + + #[test] + fn display_works() { + let bench = Benchmark::new(Method::Radau5); + assert_eq!( + format!("{}", bench), + "Radau5: Radau method (Radau IIA) (implicit, order 5, embedded)\n\ + Number of function evaluations = 0\n\ + Number of Jacobian evaluations = 0\n\ + Number of factorizations = 0\n\ + Number of lin sys solutions = 0\n\ + Number of performed steps = 0\n\ + Number of accepted steps = 0\n\ + Number of rejected steps = 0\n\ + Number of iterations (maximum) = 0\n\ + Number of iterations (last step) = 0\n\ + Last accepted/suggested stepsize = 0\n\ + Max time spent on a step = 0ns\n\ + Max time spent on the Jacobian = 0ns\n\ + Max time spent on factorization = 0ns\n\ + Max time spent on lin solution = 0ns\n\ + Total time = 0ns" + ); + let bench = Benchmark::new(Method::MdEuler); + assert_eq!( + format!("{}", bench), + "MdEuler: Modified Euler method (explicit, order 2(1), embedded)\n\ + Number of function evaluations = 0\n\ + Number of performed steps = 0\n\ + Number of accepted steps = 0\n\ + Number of rejected steps = 0\n\ + Last accepted/suggested stepsize = 0\n\ + Max time spent on a step = 0ns\n\ + Total time = 0ns" + ); + } +} diff --git a/russell_ode/src/constants.rs b/russell_ode/src/constants.rs new file mode 100644 index 00000000..31dd7002 --- /dev/null +++ b/russell_ode/src/constants.rs @@ -0,0 +1,542 @@ +use russell_lab::math::SQRT_6; + +/// Default number of steps to use when the automatic stepping is not available +pub const N_EQUAL_STEPS: usize = 10; + +// References: +// +// 1. Hairer E, Nørsett, SP, Wanner G (2008) Solving Ordinary Differential Equations I. +// Non-stiff Problems. Second Revised Edition. Corrected 3rd printing 2008. Springer Series +// in Computational Mathematics, 528p +// 2. Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. +// Stiff and Differential-Algebraic Problems. Second Revised Edition. +// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p +// 3. Kreyszig, E (2011) Advanced engineering mathematics; in collaboration with Kreyszig H, +// Edward JN 10th ed 2011, Hoboken, New Jersey, Wiley + +// Runge-Kutta -- order 2 --------------------------------------------------------------------- + +// Table 1.1 on page 135 of Ref#1 (also known as mid-point) + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_2_A: [[f64; 2]; 2] = [ + [0.0, 0.0], + [1.0 / 2.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_2_B: [f64; 2] = [0.0, 1.0]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_2_C: [f64; 2] = [0.0, 1.0 / 2.0]; + +// Runge-Kutta -- order 3 --------------------------------------------------------------------- + +// Table 1.1 on page 135 of Ref#1 (Note: this method has 4 stages) + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_3_A: [[f64; 4]; 4] = [ + [0.0, 0.0, 0.0, 0.0], + [1.0 / 2.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_3_B: [f64; 4] = [1.0 / 6.0, 2.0 / 3.0, 0.0, 1.0 / 6.0]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_3_C: [f64; 4] = [0.0, 1.0 / 2.0, 1.0, 1.0]; + +// Heun -- order 3 ---------------------------------------------------------------------------- + +// Table 1.1 on page 135 of Ref#1 + +#[rustfmt::skip] +pub(crate) const HEUN_3_A: [[f64; 3]; 3] = [ + [0.0, 0.0, 0.0], + [1.0 / 3.0, 0.0, 0.0], + [0.0, 2.0 / 3.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const HEUN_3_B: [f64; 3] = [1.0 / 4.0, 0.0, 3.0 / 4.0]; + +#[rustfmt::skip] +pub(crate) const HEUN_3_C: [f64; 3] = [0.0, 1.0 / 3.0, 2.0 / 3.0]; + +// Runge-Kutta -- order 4 --------------------------------------------------------------------- + +// Table 1.2 on page 138 of Ref#1 ("The" Runge-Kutta method) + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_4_A: [[f64; 4]; 4] = [ + [0.0, 0.0, 0.0, 0.0], + [1.0 / 2.0, 0.0, 0.0, 0.0], + [0.0, 1.0 / 2.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_4_B: [f64; 4] = [1.0 / 6.0, 2.0 / 6.0, 2.0 / 6.0, 1.0 / 6.0]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_4_C: [f64; 4] = [0.0, 1.0 / 2.0, 1.0 / 2.0, 1.0]; + +// Runge-Kutta -- alternative 3/8 rule -- order 4 --------------------------------------------- + +// Table 1.2 on page 128 of Ref#1 + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_ALT_4_A: [[f64; 4]; 4] = [ + [0.0, 0.0, 0.0, 0.0], + [1.0 / 3.0, 0.0, 0.0, 0.0], + [-1.0 / 3.0, 1.0, 0.0, 0.0], + [1.0, -1.0, 1.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_ALT_4_B: [f64; 4] = [1.0 / 8.0, 3.0 / 8.0, 3.0 / 8.0, 1.0 / 8.0]; + +#[rustfmt::skip] +pub(crate) const RUNGE_KUTTA_ALT_4_C: [f64; 4] = [0.0, 1.0 / 3.0, 2.0 / 3.0, 1.0]; + +// Modified Euler -- order 2 -- embedded 2(1) ------------------------------------------------- + +// Table 21.1 on page 903 of Ref#3 (also known as Improved Euler method) + +#[rustfmt::skip] +pub(crate) const MODIFIED_EULER_A: [[f64; 2]; 2] = [ + [0.0, 0.0], + [1.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const MODIFIED_EULER_B: [f64; 2] = [1.0 / 2.0, 1.0 / 2.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const MODIFIED_EULER_BE: [f64; 2] = [1.0, 0.0]; + +#[rustfmt::skip] +pub(crate) const MODIFIED_EULER_C: [f64; 2] = [0.0, 1.0]; + +#[rustfmt::skip] +pub(crate) const MODIFIED_EULER_E: [f64; 2] = [-1.0 / 2.0, 1.0 / 2.0]; + +// Merson -- order 4 -- embedded 4("5") or 4(3) ----------------------------------------------- + +// Table 4.1 on page 167 of Ref#1 +// Note: This is the Merson 4("5") method where "5" means that the order 5 is for linear equations +// with constant coefficients; otherwise the method is of order 3. + +#[rustfmt::skip] +pub(crate) const MERSON_4_A: [[f64; 5]; 5] = [ + [0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 3.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 6.0, 1.0 / 6.0, 0.0, 0.0, 0.0], + [1.0 / 8.0, 0.0, 3.0 / 8.0, 0.0, 0.0], + [1.0 / 2.0, 0.0, -3.0 / 2.0, 2.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const MERSON_4_B: [f64; 5] = [1.0 / 6.0, 0.0, 0.0, 2.0 / 3.0, 1.0 / 6.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const MERSON_4_BE: [f64; 5] = [1.0 / 10.0, 0.0, 3.0 / 10.0, 2.0 / 5.0, 1.0 / 5.0]; + +#[rustfmt::skip] +pub(crate) const MERSON_4_C: [f64; 5] = [0.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 2.0, 1.0]; + +#[rustfmt::skip] +pub(crate) const MERSON_4_E: [f64; 5] = [1.0 / 15.0, 0.0, -3.0 / 10.0, 4.0 / 15.0, -1.0 / 30.0]; + +// Zonneveld -- order 4 -- embedded 4(3) ------------------------------------------------------ + +// Table 4.2 on page 167 of Ref#1 + +#[rustfmt::skip] +pub(crate) const ZONNEVELD_4_A: [[f64; 5]; 5] = [ + [0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 2.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 1.0 / 2.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0], + [5.0 / 32.0, 7.0 / 32.0, 13.0 / 32.0, -1.0 / 32.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const ZONNEVELD_4_B: [f64; 5] = [1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0, 0.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const ZONNEVELD_4_BE: [f64; 5] = [-1.0 / 2.0, 7.0 / 3.0, 7.0 / 3.0, 13.0 / 6.0, -16.0 / 3.0]; + +#[rustfmt::skip] +pub(crate) const ZONNEVELD_4_C: [f64; 5] = [0.0, 1.0 / 2.0, 1.0 / 2.0, 1.0, 3.0 / 4.0]; + +#[rustfmt::skip] +pub(crate) const ZONNEVELD_4_E: [f64; 5] = [2.0 / 3.0, -2.0, -2.0, -2.0, 16.0 / 3.0]; + +// Fehlberg -- order 4 -- embedded 4(5) ------------------------------------------------------- + +// Table 5.1 on page 177 of Ref#1 + +#[rustfmt::skip] +pub(crate) const FEHLBERG_4_A: [[f64; 6]; 6] = [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 4.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [3.0 / 32.0, 9.0 / 32.0, 0.0, 0.0, 0.0, 0.0], + [1932.0 / 2197.0, -7200.0 / 2197.0, 7296.0 / 2197.0, 0.0, 0.0, 0.0], + [439.0 / 216.0, -8.0, 3680.0 / 513.0, -845.0 / 4104.0, 0.0, 0.0], + [-8.0 / 27.0, 2.0, -3544.0 / 2565.0, 1859.0 / 4104.0, -11.0 / 40.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const FEHLBERG_4_B: [f64; 6] = [25.0 / 216.0, 0.0, 1408.0 / 2565.0, 2197.0 / 4104.0, -1.0 / 5.0, 0.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const FEHLBERG_4_BE: [f64; 6] = [16.0 / 135.0, 0.0, 6656.0 / 12825.0, 28561.0 / 56430.0, -9.0 / 50.0, 2.0 / 55.0]; + +#[rustfmt::skip] +pub(crate) const FEHLBERG_4_C: [f64; 6] = [0.0, 1.0 / 4.0, 3.0 / 8.0, 12.0 / 13.0, 1.0, 1.0 / 2.0]; + +#[rustfmt::skip] +pub(crate) const FEHLBERG_4_E: [f64; 6] = [-1.0 / 360.0, 0.0, 128.0 / 4275.0, 2197.0 / 75240.0, -1.0 / 50.0, -2.0 / 55.0]; + +// Dormand-Prince -- order 5 -- embedded 5(4) FSAL -------------------------------------------- + +// Table 5.2 on page 178 of Ref#1 + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_5_A: [[f64; 7]; 7] = [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [3.0 / 40.0, 9.0 / 40.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [44.0 / 45.0, -56.0 / 15.0, 32.0 / 9.0, 0.0, 0.0, 0.0, 0.0], + [19372.0 / 6561.0, -25360.0 / 2187.0, 64448.0 / 6561.0, -212.0 / 729.0, 0.0, 0.0, 0.0], + [9017.0 / 3168.0, -355.0 / 33.0, 46732.0 / 5247.0, 49.0 / 176.0, -5103.0 / 18656.0, 0.0, 0.0], + [35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0, 11.0 / 84.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_5_B: [f64; 7] = [35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0, 11.0 / 84.0, 0.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_5_BE: [f64; 7] = [5179.0 / 57600.0, 0.0, 7571.0 / 16695.0, 393.0 / 640.0, -92097.0 / 339200.0, 187.0 / 2100.0, 1.0 / 40.0]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_5_C: [f64; 7] = [0.0, 1.0 / 5.0, 3.0 / 10.0, 4.0 / 5.0, 8.0 / 9.0, 1.0, 1.0]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_5_E: [f64; 7] = [71.0 / 57600.0, 0.0, -71.0 / 16695.0, 71.0 / 1920.0, -17253.0 / 339200.0, 22.0 / 525.0, -1.0 / 40.0]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_5_D: [[f64; 7]; 1] = [[ + -12715105075.0 / 11282082432.0, // D1 + 0.00000000000000000000000000, // D2 + 87487479700.0 / 32700410799.0, // D3 + -10690763975.0 / 1880347072.0, // D4 + 701980252875.0 / 199316789632.0, // D5 + -1453857185.0 / 822651844.0, // D6 + 69997945.0 / 29380423.0, // D7 +]]; + +// Verner -- order 6 -- embedded -- 6(5) ------------------------------------------------------ + +// Table 5.4 on page 181 of Ref#1 +// Hairer-Wanner: "[...] Fehlberg's methods suffer from the fact that they give identically zero +// error estimates for quadrature problems y' = f(x). The first high order embedded formulas +// which avoid this drawback were constructed by Verner (1978). [...]" + +#[rustfmt::skip] +pub(crate) const VERNER_6_A: [[f64; 8]; 8] = [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 6.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [4.0 / 75.0, 16.0 / 75.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [5.0 / 6.0, -8.0 / 3.0, 5.0 / 2.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [-165.0 / 64.0, 55.0 / 6.0, -425.0 / 64.0, 85.0 / 96.0, 0.0, 0.0, 0.0, 0.0], + [12.0 / 5.0, -8.0, 4015.0 / 612.0, -11.0 / 36.0, 88.0 / 255.0, 0.0, 0.0, 0.0], + [-8263.0 / 15000.0, 124.0 / 75.0, -643.0 / 680.0, -81.0 / 250.0, 2484.0 / 10625.0, 0.0, 0.0, 0.0], + [3501.0 / 1720.0, -300.0 / 43.0, 297275.0 / 52632.0, -319.0 / 2322.0, 24068.0 / 84065.0, 0.0, 3850.0 / 26703.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const VERNER_6_B: [f64; 8] = [3.0 / 40.0, 0.0, 875.0 / 2244.0, 23.0 / 72.0, 264.0 / 1955.0, 0.0, 125.0 / 11592.0, 43.0 / 616.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const VERNER_6_BE: [f64; 8] = [13.0 / 160.0, 0.0, 2375.0 / 5984.0, 5.0 / 16.0, 12.0 / 85.0, 3.0 / 44.0, 0.0, 0.0]; + +#[rustfmt::skip] +pub(crate) const VERNER_6_C: [f64; 8] = [0.0, 1.0 / 6.0, 4.0 / 15.0, 2.0 / 3.0, 5.0 / 6.0, 1.0, 1.0 / 15.0, 1.0]; + +#[rustfmt::skip] +pub(crate) const VERNER_6_E: [f64; 8] = [-1.0 / 160.0, 0.0, -125.0 / 17952.0, 1.0 / 144.0, -12.0 / 1955.0, -3.0 / 44.0, 125.0 / 11592.0, 43.0 / 616.0]; + +// Fehlberg -- order 7 -- embedded -- 7(8) ---------------------------------------------------- + +// Table 5.3 on page 180 of Ref#1 + +#[rustfmt::skip] +pub(crate) const FEHLBERG_7_A: [[f64; 13]; 13] = [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.0 / 27.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 36.0, 1.0 / 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 24.0, 0.0, 1.0 / 8.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [5.0 / 12.0, 0.0, -25.0 / 16.0, 25.0 / 16.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.0 / 20.0, 0.0, 0.0, 1.0 / 4.0, 1.0 / 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [-25.0 / 108.0, 0.0, 0.0, 125.0 / 108.0, -65.0 / 27.0, 125.0 / 54.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [31.0 / 300.0, 0.0, 0.0, 0.0, 61.0 / 225.0, -2.0 / 9.0, 13.0 / 900.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.0, 0.0, 0.0, -53.0 / 6.0, 704.0 / 45.0, -107.0 / 9.0, 67.0 / 90.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [-91.0 / 108.0, 0.0, 0.0, 23.0 / 108.0, -976.0 / 135.0, 311.0 / 54.0, -19.0 / 60.0, 17.0 / 6.0, -1.0 / 12.0, 0.0, 0.0, 0.0, 0.0], + [2383.0 / 4100.0, 0.0, 0.0, -341.0 / 164.0, 4496.0 / 1025.0, -301.0 / 82.0, 2133.0 / 4100.0, 45.0 / 82.0, 45.0 / 164.0, 18.0 / 41.0, 0.0, 0.0, 0.0], + [3.0 / 205.0, 0.0, 0.0, 0.0, 0.0, -6.0 / 41.0, -3.0 / 205.0, -3.0 / 41.0, 3.0 / 41.0, 6.0 / 41.0, 0.0, 0.0, 0.0], + [-1777.0 / 4100.0, 0.0, 0.0, -341.0 / 164.0, 4496.0 / 1025.0, -289.0 / 82.0, 2193.0 / 4100.0, 51.0 / 82.0, 33.0 / 164.0, 12.0 / 41.0, 0.0, 1.0, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const FEHLBERG_7_B: [f64; 13] = [41.0 / 840.0, 0.0, 0.0, 0.0, 0.0, 34.0 / 105.0, 9.0 / 35.0, 9.0 / 35.0, 9.0 / 280.0, 9.0 / 280.0, 41.0 / 840.0, 0.0, 0.0]; + +#[allow(unused)] +#[rustfmt::skip] +pub(crate) const FEHLBERG_7_BE: [f64; 13] = [0.0, 0.0, 0.0, 0.0, 0.0, 34.0 / 105.0, 9.0 / 35.0, 9.0 / 35.0, 9.0 / 280.0, 9.0 / 280.0, 0.0, 41.0 / 840.0, 41.0 / 840.0]; + +#[rustfmt::skip] +pub(crate) const FEHLBERG_7_C: [f64; 13] = [0.0, 2.0 / 27.0, 1.0 / 9.0, 1.0 / 6.0, 5.0 / 12.0, 1.0 / 2.0, 5.0 / 6.0, 1.0 / 6.0, 2.0 / 3.0, 1.0 / 3.0, 1.0, 0.0, 1.0]; + +#[rustfmt::skip] +pub(crate) const FEHLBERG_7_E: [f64; 13] = [41.0 / 840.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 41.0 / 840.0, -41.0 / 840.0, -41.0 / 840.0]; + +// Dormand-Prince -- order 8 -- embedded -- 8(5,3) -------------------------------------------- + +// The coefficients here are taken from dop853.f, available on Hairer's website + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_A: [[f64; 12]; 12] = [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [5.26001519587677318785587544488e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [1.97250569845378994544595329183e-2, 5.91751709536136983633785987549e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.95875854768068491816892993775e-2, 0.0, 8.87627564304205475450678981324e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [2.41365134159266685502369798665e-1, 0.0, -8.84549479328286085344864962717e-1, 9.24834003261792003115737966543e-1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [3.7037037037037037037037037037e-2, 0.0, 0.0, 1.70828608729473871279604482173e-1, 1.25467687566822425016691814123e-1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [3.71093750000000000000000000000e-2, 0.0, 0.0, 1.70252211019544039314978060272e-1, 6.02165389804559606850219397283e-2, -1.75781250000000000000000000000e-2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [3.70920001185047927108779319836e-2, 0.0, 0.0, 1.70383925712239993810214054705e-1, 1.07262030446373284651809199168e-1, -1.53194377486244017527936158236e-2, 8.27378916381402288758473766002e-3, 0.0, 0.0, 0.0, 0.0, 0.0], + [6.24110958716075717114429577812e-1, 0.0, 0.0, -3.36089262944694129406857109825, -8.68219346841726006818189891453e-1, 2.75920996994467083049415600797e1, 2.01540675504778934086186788979e1, -4.34898841810699588477366255144e1, 0.0, 0.0, 0.0, 0.0], + [4.77662536438264365890433908527e-1, 0.0, 0.0, -2.48811461997166764192642586468, -5.90290826836842996371446475743e-1, 2.12300514481811942347288949897e1, 1.52792336328824235832596922938e1, -3.32882109689848629194453265587e1, -2.03312017085086261358222928593e-2, 0.0, 0.0, 0.0], + [-9.3714243008598732571704021658e-1, 0.0, 0.0, 5.18637242884406370830023853209, 1.09143734899672957818500254654, -8.14978701074692612513997267357, -1.85200656599969598641566180701e1, 2.27394870993505042818970056734e1, 2.49360555267965238987089396762, -3.0467644718982195003823669022, 0.0, 0.0], + [2.27331014751653820792359768449, 0.0, 0.0, -1.05344954667372501984066689879e1, -2.00087205822486249909675718444, -1.79589318631187989172765950534e1, 2.79488845294199600508499808837e1, -2.85899827713502369474065508674, -8.87285693353062954433549289258, 1.23605671757943030647266201528e1, 6.43392746015763530355970484046e-1, 0.0], +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_B: [f64; 12] = [ + 5.42937341165687622380535766363e-2, + 0.0, + 0.0, + 0.0, + 0.0, + 4.45031289275240888144113950566, + 1.89151789931450038304281599044, + -5.8012039600105847814672114227, + 3.1116436695781989440891606237e-1, + -1.52160949662516078556178806805e-1, + 2.01365400804030348374776537501e-1, + 4.47106157277725905176885569043e-2, +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_C: [f64; 12] = [ + 0.0, + 2.0 * (6.0 - SQRT_6) / 135.0, + (6.0 - SQRT_6) / 45.0, + (6.0 - SQRT_6) / 30.0, + (6.0 + SQRT_6) / 30.0, + 1.0 / 3.0, + 1.0 / 4.0, + 4.0 / 13.0, + 127.0 / 195.0, + 3.0 / 5.0, + 6.0 / 7.0, + 1.0, +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_E: [f64; 12] = [ + 0.1312004499419488073250102996e-01, + 0.0, + 0.0, + 0.0, + 0.0, + -0.1225156446376204440720569753e+01, + -0.4957589496572501915214079952e+00, + 0.1664377182454986536961530415e+01, + -0.3503288487499736816886487290e+00, + 0.3341791187130174790297318841e+00, + 0.8192320648511571246570742613e-01, + -0.2235530786388629525884427845e-01, +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_D: [[f64; 16]; 4] = [ + [ + -0.84289382761090128651353491142e+01 , // d41 + 0.0 , // d42 + 0.0 , // d43 + 0.0 , // d44 + 0.0 , // d45 + 0.56671495351937776962531783590e+00 , // d46 + -0.30689499459498916912797304727e+01 , // d47 + 0.23846676565120698287728149680e+01 , // d48 + 0.21170345824450282767155149946e+01 , // d49 + -0.87139158377797299206789907490e+00 , // d410 + 0.22404374302607882758541771650e+01 , // d411 + 0.63157877876946881815570249290e+00 , // d412 + -0.88990336451333310820698117400e-01 , // d413 + 0.18148505520854727256656404962e+02 , // d414 + -0.91946323924783554000451984436e+01 , // d415 + -0.44360363875948939664310572000e+01 , // d416 + ], + [ + 0.10427508642579134603413151009e+02 , // d51 + 0.0 , // d52 + 0.0 , // d53 + 0.0 , // d54 + 0.0 , // d55 + 0.24228349177525818288430175319e+03 , // d56 + 0.16520045171727028198505394887e+03 , // d57 + -0.37454675472269020279518312152e+03 , // d58 + -0.22113666853125306036270938578e+02 , // d59 + 0.77334326684722638389603898808e+01 , // d510 + -0.30674084731089398182061213626e+02 , // d511 + -0.93321305264302278729567221706e+01 , // d512 + 0.15697238121770843886131091075e+02 , // d513 + -0.31139403219565177677282850411e+02 , // d514 + -0.93529243588444783865713862664e+01 , // d515 + 0.35816841486394083752465898540e+02 , // d516 + ], + [ + 0.19985053242002433820987653617e+02 , // d61 + 0.0 , // d62 + 0.0 , // d63 + 0.0 , // d64 + 0.0 , // d65 + -0.38703730874935176555105901742e+03 , // d66 + -0.18917813819516756882830838328e+03 , // d67 + 0.52780815920542364900561016686e+03 , // d68 + -0.11573902539959630126141871134e+02 , // d69 + 0.68812326946963000169666922661e+01 , // d610 + -0.10006050966910838403183860980e+01 , // d611 + 0.77771377980534432092869265740e+00 , // d612 + -0.27782057523535084065932004339e+01 , // d613 + -0.60196695231264120758267380846e+02 , // d614 + 0.84320405506677161018159903784e+02 , // d615 + 0.11992291136182789328035130030e+02 , // d616 + ], + [ + -0.25693933462703749003312586129e+02 , // d71 + 0.0 , // d72 + 0.0 , // d73 + 0.0 , // d74 + 0.0 , // d75 + -0.15418974869023643374053993627e+03 , // d76 + -0.23152937917604549567536039109e+03 , // d77 + 0.35763911791061412378285349910e+03 , // d78 + 0.93405324183624310003907691704e+02 , // d79 + -0.37458323136451633156875139351e+02 , // d710 + 0.10409964950896230045147246184e+03 , // d711 + 0.29840293426660503123344363579e+02 , // d712 + -0.43533456590011143754432175058e+02 , // d713 + 0.96324553959188282948394950600e+02 , // d714 + -0.39177261675615439165231486172e+02 , // d715 + -0.14972683625798562581422125276e+03 , // d716 + ], +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_AD: [[f64; 15]; 3] = [ + [ + 5.61675022830479523392909219681e-2 , // 14,1 + 0.0 , // 14.2 + 0.0 , // 14.3 + 0.0 , // 14.4 + 0.0 , // 14.5 + 0.0 , // 14.6 + 2.53500210216624811088794765333e-1 , // 14,7 + -2.46239037470802489917441475441e-1 , // 14,8 + -1.24191423263816360469010140626e-1 , // 14,9 + 1.5329179827876569731206322685e-1 , // 14,10 + 8.20105229563468988491666602057e-3 , // 14,11 + 7.56789766054569976138603589584e-3 , // 14,12 + -8.2980000000000000000000000000e-3 , // 14,13 + 0.0 , // 14.14 + 0.0 , // 14.15 + ], + [ + 3.18346481635021405060768473261e-2 , // 15,1 + 0.0 , // 15.2 + 0.0 , // 15.3 + 0.0 , // 15.4 + 0.0 , // 15.5 + 2.83009096723667755288322961402e-2 , // 15,6 + 5.35419883074385676223797384372e-2 , // 15,7 + -5.49237485713909884646569340306e-2 , // 15,8 + 0.0 , // 15.9 + 0.0 , // 15.10 + -1.08347328697249322858509316994e-4 , // 15,11 + 3.82571090835658412954920192323e-4 , // 15,12 + -3.40465008687404560802977114492e-4 , // 15,13 + 1.41312443674632500278074618366e-1 , // 15,14 + 0.0 , // 16.15 + ], + [ + -4.28896301583791923408573538692e-1 , // 16,1 + 0.0 , // 16.2 + 0.0 , // 16.3 + 0.0 , // 16.4 + 0.0 , // 16.5 + -4.69762141536116384314449447206 , // 16,6 + 7.68342119606259904184240953878 , // 16,7 + 4.06898981839711007970213554331 , // 16,8 + 3.56727187455281109270669543021e-1 , // 16,9 + 0.0 , // 16.10 + 0.0 , // 16.11 + 0.0 , // 16.12 + -1.39902416515901462129418009734e-3 , // 16,13 + 2.9475147891527723389556272149 , // 16,14 + -9.15095847217987001081870187138 , // 16,15 + ], +]; + +#[rustfmt::skip] +pub(crate) const DORMAND_PRINCE_8_CD: [f64; 3] = [ + 0.1, // c14 + 0.2, // c15 + 7.0 / 9.0, // c16 +]; + +pub(crate) const DORMAND_PRINCE_8_BHH1: f64 = 0.244094488188976377952755905512e+00; +pub(crate) const DORMAND_PRINCE_8_BHH2: f64 = 0.733846688281611857341361741547e+00; +pub(crate) const DORMAND_PRINCE_8_BHH3: f64 = 0.220588235294117647058823529412e-01; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use russell_lab::approx_eq; + + #[test] + #[rustfmt::skip] + fn ee_constants_are_correct() { + // E = B - Be + for i in 0..MODIFIED_EULER_B .len() { approx_eq(MODIFIED_EULER_E [i], MODIFIED_EULER_B [i] - MODIFIED_EULER_BE [i], 1e-15); } + for i in 0..MERSON_4_B .len() { approx_eq(MERSON_4_E [i], MERSON_4_B [i] - MERSON_4_BE [i], 1e-15); } + for i in 0..ZONNEVELD_4_B .len() { approx_eq(ZONNEVELD_4_E [i], ZONNEVELD_4_B [i] - ZONNEVELD_4_BE [i], 1e-15); } + for i in 0..FEHLBERG_4_B .len() { approx_eq(FEHLBERG_4_E [i], FEHLBERG_4_B [i] - FEHLBERG_4_BE [i], 1e-15); } + for i in 0..DORMAND_PRINCE_5_B.len() { approx_eq(DORMAND_PRINCE_5_E [i], DORMAND_PRINCE_5_B [i] - DORMAND_PRINCE_5_BE[i], 1e-15); } + for i in 0..VERNER_6_B .len() { approx_eq(VERNER_6_E [i], VERNER_6_B [i] - VERNER_6_BE [i], 1e-15); } + for i in 0..FEHLBERG_7_B .len() { approx_eq(FEHLBERG_7_E [i], FEHLBERG_7_B [i] - FEHLBERG_7_BE [i], 1e-15); } + } +} diff --git a/russell_ode/src/detect_stiffness.rs b/russell_ode/src/detect_stiffness.rs new file mode 100644 index 00000000..d822e6d5 --- /dev/null +++ b/russell_ode/src/detect_stiffness.rs @@ -0,0 +1,68 @@ +use crate::StrError; +use crate::{Params, Workspace}; + +/// Detects whether the problem becomes stiff or not +pub(crate) fn detect_stiffness(work: &mut Workspace, params: &Params) -> Result<(), StrError> { + work.stiff_detected = false; + if work.bench.n_accepted <= params.stiffness.skip_first_n_accepted_step { + return Ok(()); + } + if work.stiff_h_times_lambda > params.stiffness.h_times_lambda_max { + work.stiff_n_detection_no = 0; + work.stiff_n_detection_yes += 1; + if work.stiff_n_detection_yes == params.stiffness.ratified_after_nstep { + work.stiff_detected = true; + if params.stiffness.stop_with_error { + return Err("stiffness detected"); + } + } + } else { + work.stiff_n_detection_no += 1; + if work.stiff_n_detection_no == params.stiffness.ignored_after_nstep { + work.stiff_n_detection_yes = 0; + } + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::detect_stiffness; + use crate::{Method, Params, Workspace}; + + #[test] + fn detect_stiffness_works() { + let mut params = Params::new(Method::DoPri5); + let mut work = Workspace::new(Method::DoPri5); + + params.stiffness.skip_first_n_accepted_step = 10; + detect_stiffness(&mut work, ¶ms).unwrap(); + assert_eq!(work.stiff_detected, false); + + work.bench.n_accepted = params.stiffness.skip_first_n_accepted_step + 1; + work.stiff_h_times_lambda = 3.25 + 0.01; // DoPri5 + work.stiff_n_detection_yes = params.stiffness.ratified_after_nstep - 1; // will add 1 + assert_eq!(detect_stiffness(&mut work, ¶ms).err(), Some("stiffness detected")); + + params.stiffness.stop_with_error = false; + work.stiff_n_detection_yes = params.stiffness.ratified_after_nstep - 1; // will add 1 + detect_stiffness(&mut work, ¶ms).unwrap(); + assert_eq!(work.stiff_detected, true); + assert_eq!(work.stiff_n_detection_no, 0); + assert_eq!(work.stiff_n_detection_yes, params.stiffness.ratified_after_nstep); + + work.stiff_h_times_lambda = 3.25 - 0.01; // DoPri5 + detect_stiffness(&mut work, ¶ms).unwrap(); + assert_eq!(work.stiff_detected, false); + assert_eq!(work.stiff_n_detection_no, 1); + assert_eq!(work.stiff_n_detection_yes, params.stiffness.ratified_after_nstep); + + work.stiff_n_detection_no = params.stiffness.ignored_after_nstep - 1; // will add 1 + detect_stiffness(&mut work, ¶ms).unwrap(); + assert_eq!(work.stiff_detected, false); + assert_eq!(work.stiff_n_detection_no, params.stiffness.ignored_after_nstep); + assert_eq!(work.stiff_n_detection_yes, 0); + } +} diff --git a/russell_ode/src/enums.rs b/russell_ode/src/enums.rs new file mode 100644 index 00000000..80020218 --- /dev/null +++ b/russell_ode/src/enums.rs @@ -0,0 +1,286 @@ +/// Indicates whether the analytical Jacobian is available or not +pub enum HasJacobian { + /// Indicates that the analytical Jacobian is available + Yes, + + /// Indicates that the analytical Jacobian is not available + /// + /// **Note:** The [crate::no_jacobian] function can be conveniently used with the `No` option. + No, +} + +/// Holds information about the numerical method to solve (approximate) ODEs +#[derive(Clone, Copy, Debug)] +pub struct Information { + /// Is the order of y1 (corresponding to B); i.e., the "p" constant + pub order: usize, + + /// Is he order of error estimator (embedded only); i.e., the "q" constant + /// + /// For DoPri5(4): q = 4 = min(order(y1), order(y1bar)) + pub order_of_estimator: usize, // 0 means no error estimator available + + /// Indicates implicit method instead of explicit + pub implicit: bool, + + /// Indicates that the method has embedded error estimator + pub embedded: bool, + + /// Indicates that the method has more than one stage + pub multiple_stages: bool, + + /// Indicates that the first step's coefficient is equal to the last step's coefficient (FSAL) + /// + /// See explanation about `FSAL` in Hairer-Nørsett-Wanner Part I, pages 167 and 178 + pub first_step_same_as_last: bool, +} + +/// Specifies the numerical method to solve (approximate) ODEs +/// +/// # References +/// +/// 1. E. Hairer, S. P. Nørsett, G. Wanner (2008) Solving Ordinary Differential Equations I. +/// Non-stiff Problems. Second Revised Edition. Corrected 3rd printing 2008. Springer Series +/// in Computational Mathematics, 528p +/// 2. E. Hairer, G. Wanner (2002) Solving Ordinary Differential Equations II. +/// Stiff and Differential-Algebraic Problems. Second Revised Edition. +/// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Method { + /// Radau method (Radau IIA) (implicit, order 5, embedded) + Radau5, + + /// Backward Euler method (implicit, order 1) + BwEuler, + + /// Forward Euler method (explicit, order 1) + FwEuler, + + /// Runge (Kutta) method (mid-point) (explicit, order 2) + /// + /// Reference: page 135 of Hairer, Nørsett, and Wanner (2008) + Rk2, + + /// Runge (Kutta) method (explicit, order 3) + /// + /// Reference: page 135 of Hairer, Nørsett, and Wanner (2008) + Rk3, + + /// Heun method (explicit, order 3) + /// + /// Reference: page 135 of Hairer, Nørsett, and Wanner (2008) + Heun3, + + /// "The" Runge-Kutta method (explicit, order 4) + /// + /// Reference: page 138 of Hairer, Nørsett, and Wanner (2008) + Rk4, + + /// Runge-Kutta method (alternative) (explicit, order 4, 3/8-Rule) + /// + /// Reference: page 138 of Hairer, Nørsett, and Wanner (2008) + Rk4alt, + + /// Modified Euler method (explicit, order 2(1), embedded) + MdEuler, + + /// Merson method (explicit, order 4("5"), embedded) + /// + /// "5" means that the order 5 is for linear equations with constant coefficients; + /// otherwise the method is of order3. + /// + /// Reference: page 167 of Hairer, Nørsett, and Wanner (2008) + Merson4, + + /// Zonneveld method (explicit, order 4(3), embedded) + /// + /// Reference: page 167 of Hairer, Nørsett, and Wanner (2008) + Zonneveld4, + + /// Fehlberg method (explicit, order 4(5), embedded) + /// + /// Note: this method gives identically zero error estimates for quadrature problems `y'=f(x)` (see page 180 of ref # 1). + Fehlberg4, + + /// Dormand-Prince method (explicit, order 5(4), embedded) + DoPri5, + + /// Verner method (explicit, order 6(5), embedded) + Verner6, + + /// Fehlberg method (explicit, order 7(8), embedded) + /// + /// Note: this method gives identically zero error estimates for quadrature problems `y'=f(x)` (see page 180 of ref # 1). + Fehlberg7, + + /// Dormand-Prince method (explicit, order 8(5,3), embedded) + DoPri8, +} + +impl Method { + #[rustfmt::skip] + pub fn information(&self) -> Information { + match self { + Method::Radau5 => Information { order: 5, order_of_estimator: 4, implicit: true, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::BwEuler => Information { order: 1, order_of_estimator: 0, implicit: true, embedded: false, multiple_stages: false, first_step_same_as_last: false }, + Method::FwEuler => Information { order: 1, order_of_estimator: 0, implicit: false, embedded: false, multiple_stages: false, first_step_same_as_last: false }, + Method::Rk2 => Information { order: 2, order_of_estimator: 0, implicit: false, embedded: false, multiple_stages: true, first_step_same_as_last: false }, + Method::Rk3 => Information { order: 3, order_of_estimator: 0, implicit: false, embedded: false, multiple_stages: true, first_step_same_as_last: false }, + Method::Heun3 => Information { order: 3, order_of_estimator: 0, implicit: false, embedded: false, multiple_stages: true, first_step_same_as_last: false }, + Method::Rk4 => Information { order: 4, order_of_estimator: 0, implicit: false, embedded: false, multiple_stages: true, first_step_same_as_last: false }, + Method::Rk4alt => Information { order: 4, order_of_estimator: 0, implicit: false, embedded: false, multiple_stages: true, first_step_same_as_last: false }, + Method::MdEuler => Information { order: 2, order_of_estimator: 1, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::Merson4 => Information { order: 4, order_of_estimator: 3, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::Zonneveld4 => Information { order: 4, order_of_estimator: 3, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::Fehlberg4 => Information { order: 4, order_of_estimator: 4, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::DoPri5 => Information { order: 5, order_of_estimator: 4, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: true }, + Method::Verner6 => Information { order: 6, order_of_estimator: 5, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::Fehlberg7 => Information { order: 7, order_of_estimator: 8, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + Method::DoPri8 => Information { order: 8, order_of_estimator: 7, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }, + } + } + + pub fn description(&self) -> &'static str { + match self { + Method::Radau5 => "Radau method (Radau IIA) (implicit, order 5, embedded)", + Method::BwEuler => "Backward Euler method (implicit, order 1)", + Method::FwEuler => "Forward Euler method (explicit, order 1)", + Method::Rk2 => "Runge (Kutta) method (mid-point) (explicit, order 2)", + Method::Rk3 => "Runge (Kutta) method (explicit, order 3)", + Method::Heun3 => "Heun method (explicit, order 3)", + Method::Rk4 => "(The) Runge-Kutta method (explicit, order 4)", + Method::Rk4alt => "Runge-Kutta method (alternative) (explicit, order 4, 3/8-Rule)", + Method::MdEuler => "Modified Euler method (explicit, order 2(1), embedded)", + Method::Merson4 => "Merson method (explicit, order 4('5'), embedded)", + Method::Zonneveld4 => "Zonneveld method (explicit, order 4(3), embedded)", + Method::Fehlberg4 => "Fehlberg method (explicit, order 4(5), embedded)", + Method::DoPri5 => "Dormand-Prince method (explicit, order 5(4), embedded)", + Method::Verner6 => "Verner method (explicit, order 6(5), embedded)", + Method::Fehlberg7 => "Fehlberg method (explicit, order 7(8), embedded)", + Method::DoPri8 => "Dormand-Prince method (explicit, order 8(5,3), embedded)", + } + } + + pub fn explicit_methods() -> Vec { + vec![ + Method::FwEuler, + Method::Rk2, + Method::Rk3, + Method::Heun3, + Method::Rk4, + Method::Rk4alt, + Method::MdEuler, + Method::Merson4, + Method::Zonneveld4, + Method::Fehlberg4, + Method::DoPri5, + Method::Verner6, + Method::Fehlberg7, + Method::DoPri8, + ] + } + + pub fn implicit_methods() -> Vec { + vec![Method::Radau5, Method::BwEuler] + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn information_clone_copy_and_debug_work() { + let info = Information { + order: 2, + order_of_estimator: 1, + implicit: false, + embedded: true, + multiple_stages: true, + first_step_same_as_last: false, + }; + let copy = info; + let clone = info.clone(); + assert_eq!(format!("{:?}", info), "Information { order: 2, order_of_estimator: 1, implicit: false, embedded: true, multiple_stages: true, first_step_same_as_last: false }"); + assert_eq!(copy.order, 2); + assert_eq!(copy.order_of_estimator, 1); + assert_eq!(copy.implicit, false); + assert_eq!(copy.embedded, true); + assert_eq!(copy.first_step_same_as_last, false); + assert_eq!(clone.order, 2); + assert_eq!(clone.order_of_estimator, 1); + assert_eq!(clone.implicit, false); + assert_eq!(clone.embedded, true); + assert_eq!(clone.first_step_same_as_last, false); + } + + #[test] + fn method_clone_copy_and_debug_work() { + let method = Method::BwEuler; + let copy = method; + let clone = method.clone(); + assert_eq!(format!("{:?}", method), "BwEuler"); + assert_eq!(copy, Method::BwEuler); + assert_eq!(clone, Method::BwEuler); + } + + #[test] + #[rustfmt::skip] + fn methods_information_works() { + let m = Method::Radau5 ; let i=m.information(); assert_eq!(i.order,5); assert_eq!(i.order_of_estimator,4); assert_eq!(i.implicit,true,); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::BwEuler ; let i=m.information(); assert_eq!(i.order,1); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,true,); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,false); assert_eq!(i.first_step_same_as_last,false); + let m = Method::FwEuler ; let i=m.information(); assert_eq!(i.order,1); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,false); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,false); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Rk2 ; let i=m.information(); assert_eq!(i.order,2); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,false); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Rk3 ; let i=m.information(); assert_eq!(i.order,3); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,false); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Heun3 ; let i=m.information(); assert_eq!(i.order,3); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,false); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Rk4 ; let i=m.information(); assert_eq!(i.order,4); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,false); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Rk4alt ; let i=m.information(); assert_eq!(i.order,4); assert_eq!(i.order_of_estimator,0); assert_eq!(i.implicit,false); assert_eq!(i.embedded,false); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::MdEuler ; let i=m.information(); assert_eq!(i.order,2); assert_eq!(i.order_of_estimator,1); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Merson4 ; let i=m.information(); assert_eq!(i.order,4); assert_eq!(i.order_of_estimator,3); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Zonneveld4; let i=m.information(); assert_eq!(i.order,4); assert_eq!(i.order_of_estimator,3); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Fehlberg4 ; let i=m.information(); assert_eq!(i.order,4); assert_eq!(i.order_of_estimator,4); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::DoPri5 ; let i=m.information(); assert_eq!(i.order,5); assert_eq!(i.order_of_estimator,4); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,true ); + let m = Method::Verner6 ; let i=m.information(); assert_eq!(i.order,6); assert_eq!(i.order_of_estimator,5); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::Fehlberg7 ; let i=m.information(); assert_eq!(i.order,7); assert_eq!(i.order_of_estimator,8); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + let m = Method::DoPri8 ; let i=m.information(); assert_eq!(i.order,8); assert_eq!(i.order_of_estimator,7); assert_eq!(i.implicit,false); assert_eq!(i.embedded,true ); assert_eq!(i.multiple_stages,true ); assert_eq!(i.first_step_same_as_last,false); + } + + #[test] + fn explicit_and_implicit_methods_work() { + let explicit = Method::explicit_methods(); + let implicit = Method::implicit_methods(); + assert_eq!(explicit.len() + implicit.len(), 16); + assert_eq!( + explicit, + &[ + Method::FwEuler, + Method::Rk2, + Method::Rk3, + Method::Heun3, + Method::Rk4, + Method::Rk4alt, + Method::MdEuler, + Method::Merson4, + Method::Zonneveld4, + Method::Fehlberg4, + Method::DoPri5, + Method::Verner6, + Method::Fehlberg7, + Method::DoPri8, + ] + ); + assert_eq!(implicit, &[Method::Radau5, Method::BwEuler]); + } + + #[test] + fn description_works() { + for m in Method::explicit_methods() { + assert!(m.description().len() > 0); + } + for m in Method::implicit_methods() { + assert!(m.description().len() > 0); + } + } +} diff --git a/russell_ode/src/erk_dense_out.rs b/russell_ode/src/erk_dense_out.rs new file mode 100644 index 00000000..3cd6895f --- /dev/null +++ b/russell_ode/src/erk_dense_out.rs @@ -0,0 +1,351 @@ +use crate::StrError; +use crate::{Method, System}; +use crate::{DORMAND_PRINCE_5_D, DORMAND_PRINCE_8_AD, DORMAND_PRINCE_8_CD, DORMAND_PRINCE_8_D}; +use russell_lab::Vector; +use russell_sparse::CooMatrix; + +/// Handles the dense output of explicit Runge-Kutta methods +pub(crate) struct ErkDenseOut { + /// Holds the method + method: Method, + + /// System dimension + ndim: usize, + + /// Dense output values (nstage_dense * ndim) + d: Vec, + + /// k values for dense output + kd: Vec, + + /// y values for dense output + yd: Vector, +} + +impl ErkDenseOut { + /// Allocates a new instance + pub(crate) fn new(method: Method, ndim: usize) -> Result { + match method { + Method::Radau5 => Err("INTERNAL ERROR: cannot use Radau5 with ErkDenseOut"), + Method::BwEuler => Err("INTERNAL ERROR: cannot use BwEuler with ErkDenseOut"), + Method::FwEuler => Err("INTERNAL ERROR: cannot use FwEuler with ErkDenseOut"), + Method::Rk2 => Err("dense output is not available for the Rk2 method"), + Method::Rk3 => Err("dense output is not available for the Rk3 method"), + Method::Heun3 => Err("dense output is not available for the Heun3 method"), + Method::Rk4 => Err("dense output is not available for the Rk4 method"), + Method::Rk4alt => Err("dense output is not available for the Rk4alt method"), + Method::MdEuler => Err("dense output is not available for the MdEuler method"), + Method::Merson4 => Err("dense output is not available for the Merson4 method"), + Method::Zonneveld4 => Err("dense output is not available for the Zonneveld4 method"), + Method::Fehlberg4 => Err("dense output is not available for the Fehlberg4 method"), + Method::DoPri5 => Ok(ErkDenseOut { + method, + ndim, + d: vec![Vector::new(ndim); 5], + kd: Vec::new(), + yd: Vector::new(0), + }), + Method::Verner6 => Err("dense output is not available for the Verner6 method"), + Method::Fehlberg7 => Err("dense output is not available for the Fehlberg7 method"), + Method::DoPri8 => Ok(ErkDenseOut { + method, + ndim, + d: vec![Vector::new(ndim); 8], + kd: vec![Vector::new(ndim); 3], + yd: Vector::new(ndim), + }), + } + } + + /// Updates the data and returns the number of function evaluations + pub(crate) fn update<'a, F, J, A>( + &mut self, + system: &System, + x: f64, + y: &Vector, + h: f64, + w: &Vector, + k: &Vec, + args: &mut A, + ) -> Result + where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, + { + let mut n_function_eval = 0; + + if self.method == Method::DoPri5 { + let dd = &DORMAND_PRINCE_5_D; + for m in 0..self.ndim { + let y_diff = w[m] - y[m]; + let b_spl = h * k[0][m] - y_diff; + self.d[0][m] = y[m]; + self.d[1][m] = y_diff; + self.d[2][m] = b_spl; + self.d[3][m] = y_diff - h * k[6][m] - b_spl; + self.d[4][m] = dd[0][0] * k[0][m] + + dd[0][2] * k[2][m] + + dd[0][3] * k[3][m] + + dd[0][4] * k[4][m] + + dd[0][5] * k[5][m] + + dd[0][6] * k[6][m]; + self.d[4][m] *= h; + } + } + + if self.method == Method::DoPri8 { + let aad = &DORMAND_PRINCE_8_AD; + let ccd = &DORMAND_PRINCE_8_CD; + let dd = &DORMAND_PRINCE_8_D; + + // first function evaluation + for m in 0..self.ndim { + self.yd[m] = y[m] + + h * (aad[0][0] * k[0][m] + + aad[0][6] * k[6][m] + + aad[0][7] * k[7][m] + + aad[0][8] * k[8][m] + + aad[0][9] * k[9][m] + + aad[0][10] * k[10][m] + + aad[0][11] * k[11][m] + + aad[0][12] * k[11][m]); + } + let u = x + ccd[0] * h; + (system.function)(&mut self.kd[0], u, &self.yd, args)?; + + // second function evaluation + for m in 0..self.ndim { + self.yd[m] = y[m] + + h * (aad[1][0] * k[0][m] + + aad[1][5] * k[5][m] + + aad[1][6] * k[6][m] + + aad[1][7] * k[7][m] + + aad[1][10] * k[10][m] + + aad[1][11] * k[11][m] + + aad[1][12] * k[11][m] + + aad[1][13] * self.kd[0][m]); + } + let u = x + ccd[1] * h; + (system.function)(&mut self.kd[1], u, &self.yd, args)?; + + // next third function evaluation + for m in 0..self.ndim { + self.yd[m] = y[m] + + h * (aad[2][0] * k[0][m] + + aad[2][5] * k[5][m] + + aad[2][6] * k[6][m] + + aad[2][7] * k[7][m] + + aad[2][8] * k[8][m] + + aad[2][12] * k[11][m] + + aad[2][13] * self.kd[0][m] + + aad[2][14] * self.kd[1][m]); + } + let u = x + ccd[2] * h; + (system.function)(&mut self.kd[2], u, &self.yd, args)?; + + // final results + for m in 0..self.ndim { + let y_diff = w[m] - y[m]; + let b_spl = h * k[0][m] - y_diff; + self.d[0][m] = y[m]; + self.d[1][m] = y_diff; + self.d[2][m] = b_spl; + self.d[3][m] = y_diff - h * k[11][m] - b_spl; + self.d[4][m] = h + * (dd[0][0] * k[0][m] + + dd[0][5] * k[5][m] + + dd[0][6] * k[6][m] + + dd[0][7] * k[7][m] + + dd[0][8] * k[8][m] + + dd[0][9] * k[9][m] + + dd[0][10] * k[10][m] + + dd[0][11] * k[11][m] + + dd[0][12] * k[11][m] + + dd[0][13] * self.kd[0][m] + + dd[0][14] * self.kd[1][m] + + dd[0][15] * self.kd[2][m]); + self.d[5][m] = h + * (dd[1][0] * k[0][m] + + dd[1][5] * k[5][m] + + dd[1][6] * k[6][m] + + dd[1][7] * k[7][m] + + dd[1][8] * k[8][m] + + dd[1][9] * k[9][m] + + dd[1][10] * k[10][m] + + dd[1][11] * k[11][m] + + dd[1][12] * k[11][m] + + dd[1][13] * self.kd[0][m] + + dd[1][14] * self.kd[1][m] + + dd[1][15] * self.kd[2][m]); + self.d[6][m] = h + * (dd[2][0] * k[0][m] + + dd[2][5] * k[5][m] + + dd[2][6] * k[6][m] + + dd[2][7] * k[7][m] + + dd[2][8] * k[8][m] + + dd[2][9] * k[9][m] + + dd[2][10] * k[10][m] + + dd[2][11] * k[11][m] + + dd[2][12] * k[11][m] + + dd[2][13] * self.kd[0][m] + + dd[2][14] * self.kd[1][m] + + dd[2][15] * self.kd[2][m]); + self.d[7][m] = h + * (dd[3][0] * k[0][m] + + dd[3][5] * k[5][m] + + dd[3][6] * k[6][m] + + dd[3][7] * k[7][m] + + dd[3][8] * k[8][m] + + dd[3][9] * k[9][m] + + dd[3][10] * k[10][m] + + dd[3][11] * k[11][m] + + dd[3][12] * k[11][m] + + dd[3][13] * self.kd[0][m] + + dd[3][14] * self.kd[1][m] + + dd[3][15] * self.kd[2][m]); + } + n_function_eval = 3; + } + + Ok(n_function_eval) + } + + /// Calculates the dense output + pub(crate) fn calculate(&self, y_out: &mut Vector, x_out: f64, x: f64, h: f64) { + if self.method == Method::DoPri5 { + let x_prev = x - h; + let theta = (x_out - x_prev) / h; + let u_theta = 1.0 - theta; + for m in 0..self.ndim { + y_out[m] = self.d[0][m] + + theta + * (self.d[1][m] + u_theta * (self.d[2][m] + theta * (self.d[3][m] + u_theta * self.d[4][m]))); + } + } + if self.method == Method::DoPri8 { + let x_prev = x - h; + let theta = (x_out - x_prev) / h; + let u_theta = 1.0 - theta; + for m in 0..self.ndim { + let par = self.d[4][m] + theta * (self.d[5][m] + u_theta * (self.d[6][m] + theta * self.d[7][m])); + y_out[m] = self.d[0][m] + + theta * (self.d[1][m] + u_theta * (self.d[2][m] + theta * (self.d[3][m] + u_theta * par))); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::ErkDenseOut; + use crate::{no_jacobian, HasJacobian, Method, System}; + use russell_lab::Vector; + + #[test] + fn new_captures_errors() { + assert_eq!( + ErkDenseOut::new(Method::Radau5, 1).err(), + Some("INTERNAL ERROR: cannot use Radau5 with ErkDenseOut") + ); + assert_eq!( + ErkDenseOut::new(Method::BwEuler, 1).err(), + Some("INTERNAL ERROR: cannot use BwEuler with ErkDenseOut") + ); + assert_eq!( + ErkDenseOut::new(Method::FwEuler, 1).err(), + Some("INTERNAL ERROR: cannot use FwEuler with ErkDenseOut") + ); + assert_eq!( + ErkDenseOut::new(Method::Rk2, 1).err(), + Some("dense output is not available for the Rk2 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Rk3, 1).err(), + Some("dense output is not available for the Rk3 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Heun3, 1).err(), + Some("dense output is not available for the Heun3 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Rk4, 1).err(), + Some("dense output is not available for the Rk4 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Rk4alt, 1).err(), + Some("dense output is not available for the Rk4alt method") + ); + assert_eq!( + ErkDenseOut::new(Method::MdEuler, 1).err(), + Some("dense output is not available for the MdEuler method") + ); + assert_eq!( + ErkDenseOut::new(Method::Merson4, 1).err(), + Some("dense output is not available for the Merson4 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Zonneveld4, 1).err(), + Some("dense output is not available for the Zonneveld4 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Fehlberg4, 1).err(), + Some("dense output is not available for the Fehlberg4 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Verner6, 1).err(), + Some("dense output is not available for the Verner6 method") + ); + assert_eq!( + ErkDenseOut::new(Method::Fehlberg7, 1).err(), + Some("dense output is not available for the Fehlberg7 method") + ); + } + + #[test] + fn update_captures_errors() { + struct Args { + count: usize, + fail: usize, + } + let mut system = System::new( + 1, + |_f: &mut Vector, _x: f64, _y: &Vector, args: &mut Args| { + args.count += 1; + if args.count == args.fail { + Err("STOP") + } else { + Ok(()) + } + }, + no_jacobian, + HasJacobian::No, + None, + None, + ); + let mut out = ErkDenseOut::new(Method::DoPri8, system.ndim).unwrap(); + let mut args = Args { count: 0, fail: 1 }; + let h = 0.1; + let y = Vector::new(system.ndim); + let w = Vector::new(system.ndim); + let nstage = 12; + let k = vec![Vector::new(system.ndim); nstage]; + assert_eq!( + out.update(&mut system, 0.0, &y, h, &w, &k, &mut args).err(), + Some("STOP") + ); + args.count = 0; + args.fail = 2; + assert_eq!( + out.update(&mut system, 0.0, &y, h, &w, &k, &mut args).err(), + Some("STOP") + ); + args.count = 0; + args.fail = 3; + assert_eq!( + out.update(&mut system, 0.0, &y, h, &w, &k, &mut args).err(), + Some("STOP") + ); + } +} diff --git a/russell_ode/src/euler_backward.rs b/russell_ode/src/euler_backward.rs new file mode 100644 index 00000000..0a6eb880 --- /dev/null +++ b/russell_ode/src/euler_backward.rs @@ -0,0 +1,377 @@ +use crate::StrError; +use crate::{OdeSolverTrait, Params, System, Workspace}; +use russell_lab::{vec_copy, vec_rms_scaled, vec_update, Vector}; +use russell_sparse::{CooMatrix, LinSolver, SparseMatrix}; + +/// Implements the backward Euler (implicit) solver +pub(crate) struct EulerBackward<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Holds the parameters + params: Params, + + /// ODE system + system: &'a System, + + /// Vector holding the function evaluation + /// + /// k := f(x_new, y_new) + k: Vector, + + /// Auxiliary workspace (will contain y to be used in accept_update) + w: Vector, + + /// Residual vector (right-hand side vector) + r: Vector, + + /// Unknowns vector (the solution of the linear system) + dy: Vector, + + /// Coefficient matrix K = h J - I + kk: SparseMatrix, + + /// Linear solver + solver: LinSolver<'a>, +} + +impl<'a, F, J, A> EulerBackward<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Allocates a new instance + pub fn new(params: Params, system: &'a System) -> Self { + let ndim = system.ndim; + let jac_nnz = if params.newton.use_numerical_jacobian { + ndim * ndim + } else { + system.jac_nnz + }; + let nnz = jac_nnz + ndim; // +ndim corresponds to the diagonal I matrix + let symmetry = Some(system.jac_symmetry); + EulerBackward { + params, + system, + k: Vector::new(ndim), + w: Vector::new(ndim), + r: Vector::new(ndim), + dy: Vector::new(ndim), + kk: SparseMatrix::new_coo(ndim, ndim, nnz, symmetry).unwrap(), + solver: LinSolver::new(params.newton.genie).unwrap(), + } + } +} + +impl<'a, F, J, A> OdeSolverTrait for EulerBackward<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Enables dense output + fn enable_dense_output(&mut self) -> Result<(), StrError> { + Err("dense output is not available for the BwEuler method") + } + + /// Calculates the quantities required to update x and y + fn step(&mut self, work: &mut Workspace, x: f64, y: &Vector, h: f64, args: &mut A) -> Result<(), StrError> { + // auxiliary + let traditional_newton = !self.params.bweuler.use_modified_newton; + let ndim = self.system.ndim; + + // trial update + let x_new = x + h; + let y_new = &mut self.w; + vec_copy(y_new, &y).unwrap(); + + // perform iterations + let mut success = false; + work.bench.n_iterations = 0; + for _ in 0..self.params.newton.n_iteration_max { + // benchmark + work.bench.n_iterations += 1; + + // calculate k_new + work.bench.n_function += 1; + (self.system.function)(&mut self.k, x_new, y_new, args)?; // k := f(x_new, y_new) + + // calculate the residual and its norm + for i in 0..ndim { + self.r[i] = y_new[i] - y[i] - h * self.k[i]; + } + let r_norm = vec_rms_scaled(&self.r, y, self.params.tol.abs, self.params.tol.rel); + + // check convergence + if r_norm < self.params.tol.newton { + success = true; + break; + } + + // compute K matrix (augmented Jacobian) + if traditional_newton || work.bench.n_accepted == 0 { + // benchmark + work.bench.sw_jacobian.reset(); + work.bench.n_jacobian += 1; + + // calculate J_new := h J + let kk = self.kk.get_coo_mut().unwrap(); + if self.params.newton.use_numerical_jacobian || !self.system.jac_available { + work.bench.n_function += ndim; + let aux = &mut self.dy; // using dy as a workspace + self.system + .numerical_jacobian(kk, x_new, y_new, &self.k, h, args, aux)?; + } else { + (self.system.jacobian)(kk, x_new, y_new, h, args)?; + } + + // add diagonal entries => calculate K = h J_new - I + for i in 0..self.system.ndim { + kk.put(i, i, -1.0).unwrap(); + } + + // benchmark + work.bench.stop_sw_jacobian(); + + // perform factorization + work.bench.sw_factor.reset(); + work.bench.n_factor += 1; + self.solver + .actual + .factorize(&mut self.kk, self.params.newton.lin_sol_params)?; + work.bench.stop_sw_factor(); + } + + // solve the linear system + work.bench.sw_lin_sol.reset(); + work.bench.n_lin_sol += 1; + self.solver.actual.solve(&mut self.dy, &self.kk, &self.r, false)?; + work.bench.stop_sw_lin_sol(); + + // update y + vec_update(y_new, 1.0, &self.dy).unwrap(); // y := y + δy + } + + // check + work.bench.update_n_iterations_max(); + if !success { + return Err("Newton-Raphson method did not complete successfully"); + } + Ok(()) + } + + /// Updates x and y and computes the next stepsize + fn accept( + &mut self, + _work: &mut Workspace, + x: &mut f64, + y: &mut Vector, + h: f64, + _args: &mut A, + ) -> Result<(), StrError> { + *x += h; + vec_copy(y, &self.w).unwrap(); + Ok(()) + } + + /// Rejects the update + fn reject(&mut self, _work: &mut Workspace, _h: f64) {} + + /// Computes the dense output with x-h ≤ x_out ≤ x + fn dense_output(&self, _y_out: &mut Vector, _x_out: f64, _x: f64, _y: &Vector, _h: f64) {} +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::EulerBackward; + use crate::{Method, OdeSolverTrait, Params, Samples, Workspace}; + use russell_lab::{vec_approx_eq, Vector}; + + // Mathematica code: + // + // (* The code below works with 2 equations only *) + // BwEulerTwoEqs[f_, x0_, y0_, x1_, h_] := Module[{x, y, nstep, sol}, + // x[1] = x0; + // y[1] = y0; + // nstep = IntegerPart[(x1 - x0)/h] + 1; + // Do[ + // x[i + 1] = x[i] + h; + // sol = NSolve[{Y1, Y2} - y[i] - h f[x[i + 1], {Y1, Y2}] == 0, {Y1, Y2}][[1]]; + // y[i + 1] = {Y1, Y2} /. sol; + // , {i, 1, nstep}]; + // Table[{x[i], y[i]}, {i, 1, nstep}] + // ]; + // + // f[x_, y_] := {y[[2]], -10 y[[1]] - 11 y[[2]] + 10 x + 11}; + // x0 = 0; x1 = 2.0; + // y0 = {2, -10}; + // h = 0.4; + // + // xyBE = BwEulerTwoEqs[f, x0, y0, x1, h]; + // Print["x = ", NumberForm[xyBE[[All, 1]], 20]] + // Print["y1 = ", NumberForm[xyBE[[All, 2]][[All, 1]], 20]] + // Print["y2 = ", NumberForm[xyBE[[All, 2]][[All, 2]], 20]] + // + // ana = {y1 -> Function[{x}, Exp[-x] + Exp[-10 x] + x], y2 -> Function[{x}, -Exp[-x] - 10 Exp[-10 x] + 1]}; + // + // yBE = xyBE[[All, 2]]; + // yAna = {(y1 /. ana)[#[[1]]], (y2 /. ana)[#[[1]]]} & /@ xyBE; + // errY1 = Abs[yBE - yAna][[All, 1]]; + // errY2 = Abs[yBE - yAna][[All, 2]]; + // Print["errY1 = ", NumberForm[errY1, 20]] + // Print["errY2 = ", NumberForm[errY2, 20]] + + const XX_MATH: [f64; 6] = [0.0, 0.4, 0.8, 1.2, 1.6, 2.0]; + const YY0_MATH: [f64; 6] = [ + 2.0, + 1.314285714285715, + 1.350204081632653, + 1.572431486880467, + 1.861908204914619, + 2.186254432081871, + ]; + const YY1_MATH: [f64; 6] = [ + -10.0, + -1.714285714285714, + 0.0897959183673469, + 0.5555685131195336, + 0.723691795085381, + 0.810865567918129, + ]; + const ERR_Y0_MATH: [f64; 6] = [ + 0.0, + 0.2256500293613408, + 0.100539654887529, + 0.07123113075591125, + 0.06001157438478888, + 0.05091914678410436, + ]; + const ERR_Y1_MATH: [f64; 6] = [ + 0.0, + 1.860809279362733, + 0.4575204912364065, + 0.1431758328447311, + 0.07441056156821646, + 0.05379912823372179, + ]; + + #[test] + fn euler_backward_works() { + // This test relates to Table 21.13 of Kreyszig's book, page 921 + + // problem + let (system, data, mut args) = Samples::kreyszig_ex4_page920(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let params = Params::new(Method::BwEuler); + let mut solver = EulerBackward::new(params, &system); + let mut work = Workspace::new(Method::BwEuler); + + // check dense output availability + assert_eq!( + solver.enable_dense_output().err(), + Some("dense output is not available for the BwEuler method") + ); + + // numerical approximation + let h = 0.4; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut xx = vec![x]; + let mut yy0_num = vec![y[0]]; + let mut yy1_num = vec![y[1]]; + let mut y_ana = Vector::new(ndim); + yfx(&mut y_ana, x); + let mut err_y0 = vec![f64::abs(yy0_num[0] - y_ana[0])]; + let mut err_y1 = vec![f64::abs(yy1_num[0] - y_ana[1])]; + for n in 0..5 { + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + assert_eq!(work.bench.n_iterations, 2); + assert_eq!(work.bench.n_function, (n + 1) * 2); + assert_eq!(work.bench.n_jacobian, (n + 1)); // already converged before calling Jacobian again + + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + xx.push(x); + yy0_num.push(y[0]); + yy1_num.push(y[1]); + + yfx(&mut y_ana, x); + err_y0.push(f64::abs(yy0_num.last().unwrap() - y_ana[0])); + err_y1.push(f64::abs(yy1_num.last().unwrap() - y_ana[1])); + } + + // compare with Mathematica results + vec_approx_eq(&xx, &XX_MATH, 1e-15); + vec_approx_eq(&yy0_num, &YY0_MATH, 1e-15); + vec_approx_eq(&yy1_num, &YY1_MATH, 1e-14); + vec_approx_eq(&err_y0, &ERR_Y0_MATH, 1e-15); + vec_approx_eq(&err_y1, &ERR_Y1_MATH, 1e-14); + } + + #[test] + fn euler_backward_works_num_jacobian() { + // This test relates to Table 21.13 of Kreyszig's book, page 921 + + // problem + let (system, data, mut args) = Samples::kreyszig_ex4_page920(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let mut params = Params::new(Method::BwEuler); + params.newton.use_numerical_jacobian = true; + let mut solver = EulerBackward::new(params, &system); + let mut work = Workspace::new(Method::BwEuler); + + // numerical approximation + let h = 0.4; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut xx = vec![x]; + let mut yy0_num = vec![y[0]]; + let mut yy1_num = vec![y[1]]; + let mut y_ana = Vector::new(ndim); + yfx(&mut y_ana, x); + let mut err_y0 = vec![f64::abs(yy0_num[0] - y_ana[0])]; + let mut err_y1 = vec![f64::abs(yy1_num[0] - y_ana[1])]; + for n in 0..5 { + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + assert_eq!(work.bench.n_iterations, 2); + assert_eq!(work.bench.n_function, (n + 1) * 2 * ndim); + assert_eq!(work.bench.n_jacobian, (n + 1)); // already converged before calling Jacobian again + + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + xx.push(x); + yy0_num.push(y[0]); + yy1_num.push(y[1]); + + yfx(&mut y_ana, x); + err_y0.push(f64::abs(yy0_num.last().unwrap() - y_ana[0])); + err_y1.push(f64::abs(yy1_num.last().unwrap() - y_ana[1])); + } + + // compare with Mathematica results + vec_approx_eq(&xx, &XX_MATH, 1e-15); + vec_approx_eq(&yy0_num, &YY0_MATH, 1e-7); + vec_approx_eq(&yy1_num, &YY1_MATH, 1e-6); + vec_approx_eq(&err_y0, &ERR_Y0_MATH, 1e-7); + vec_approx_eq(&err_y1, &ERR_Y1_MATH, 1e-6); + } + + #[test] + fn euler_backward_captures_failed_iterations() { + let mut params = Params::new(Method::BwEuler); + let (system, data, mut args) = Samples::kreyszig_ex4_page920(); + params.newton.n_iteration_max = 0; + let mut solver = EulerBackward::new(params, &system); + let mut work = Workspace::new(Method::BwEuler); + assert_eq!( + solver.step(&mut work, data.x0, &data.y0, 0.1, &mut args).err(), + Some("Newton-Raphson method did not complete successfully") + ); + } +} diff --git a/russell_ode/src/euler_forward.rs b/russell_ode/src/euler_forward.rs new file mode 100644 index 00000000..a2d35522 --- /dev/null +++ b/russell_ode/src/euler_forward.rs @@ -0,0 +1,165 @@ +use crate::StrError; +use crate::{OdeSolverTrait, System, Workspace}; +use russell_lab::{vec_add, vec_copy, Vector}; +use russell_sparse::CooMatrix; + +pub(crate) struct EulerForward<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// ODE system + system: &'a System, + + /// Vector holding the function evaluation + /// + /// k := f(x, y) + k: Vector, + + /// Auxiliary workspace (will contain y to be used in accept_update) + w: Vector, +} + +impl<'a, F, J, A> EulerForward<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Allocates a new instance + pub fn new(system: &'a System) -> Self { + let ndim = system.ndim; + EulerForward { + system, + k: Vector::new(ndim), + w: Vector::new(ndim), + } + } +} + +impl<'a, F, J, A> OdeSolverTrait for EulerForward<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Enables dense output + fn enable_dense_output(&mut self) -> Result<(), StrError> { + Err("dense output is not available for the FwEuler method") + } + + /// Calculates the quantities required to update x and y + fn step(&mut self, work: &mut Workspace, x: f64, y: &Vector, h: f64, args: &mut A) -> Result<(), StrError> { + work.bench.n_function += 1; + (self.system.function)(&mut self.k, x, y, args)?; // k := f(x, y) + vec_add(&mut self.w, 1.0, &y, h, &self.k).unwrap(); // w := y + h * f(x, y) + Ok(()) + } + + /// Updates x and y and computes the next stepsize + fn accept( + &mut self, + _work: &mut Workspace, + x: &mut f64, + y: &mut Vector, + h: f64, + _args: &mut A, + ) -> Result<(), StrError> { + *x += h; + vec_copy(y, &self.w).unwrap(); + Ok(()) + } + + /// Rejects the update + fn reject(&mut self, _work: &mut Workspace, _h: f64) {} + + /// Computes the dense output with x-h ≤ x_out ≤ x + fn dense_output(&self, _y_out: &mut Vector, _x_out: f64, _x: f64, _y: &Vector, _h: f64) {} +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::EulerForward; + use crate::{Method, OdeSolverTrait, Samples, Workspace}; + use russell_lab::{vec_approx_eq, Vector}; + + #[test] + fn euler_forward_works() { + // This test relates to Table 21.2 of Kreyszig's book, page 904 + + // problem + let (system, data, mut args) = Samples::kreyszig_eq6_page902(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let mut solver = EulerForward::new(&system); + let mut work = Workspace::new(Method::FwEuler); + + // check dense output availability + assert_eq!( + solver.enable_dense_output().err(), + Some("dense output is not available for the FwEuler method") + ); + + // numerical approximation + let h = 0.2; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + yfx(&mut y_ana, x); + let mut xx = vec![x]; + let mut yy_num = vec![y[0]]; + let mut yy_ana = vec![y_ana[0]]; + let mut errors = vec![f64::abs(yy_num[0] - yy_ana[0])]; + for n in 0..5 { + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + assert_eq!(work.bench.n_function, n + 1); + + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + xx.push(x); + yy_num.push(y[0]); + + yfx(&mut y_ana, x); + yy_ana.push(y_ana[0]); + errors.push(f64::abs(yy_num.last().unwrap() - yy_ana.last().unwrap())); + } + + // Mathematica code: + // + // FwEulerSingleEq[f_, x0_, y0_, x1_, h_] := Module[{x, y, nstep}, + // x[1] = x0; + // y[1] = y0; + // nstep = IntegerPart[(x1 - x0)/h] + 1; + // Do[ + // x[i + 1] = x[i] + h; + // y[i + 1] = y[i] + h f[x[i], y[i]]; + // , {i, 1, nstep}]; + // Table[{x[i], y[i]}, {i, 1, nstep}] + // ]; + // + // f[x_, y_] := x + y; + // x0 = 0; y0 = 0; x1 = 1; h = 0.2; + // xy = FwEulerSingleEq[f, x0, y0, x1, h]; + // err = Abs[#[[2]] - (Exp[#[[1]]] - #[[1]] - 1)] & /@ xy; + // + // Print["x = ", NumberForm[xy[[All, 1]], 20]] + // Print["y = ", NumberForm[xy[[All, 2]], 20]] + // Print["err = ", NumberForm[err, 20]] + + // compare with Mathematica results + let xx_correct = &[0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; + let yy_correct = &[0.0, 0.0, 0.04000000000000001, 0.128, 0.2736000000000001, 0.48832]; + let errors_correct = &[ + 0.0, + 0.0214027581601699, + 0.05182469764127042, + 0.094118800390509, + 0.1519409284924678, + 0.229961828459045, + ]; + vec_approx_eq(&xx, xx_correct, 1e-15); + vec_approx_eq(&yy_num, yy_correct, 1e-15); + vec_approx_eq(&errors, errors_correct, 1e-15); + } +} diff --git a/russell_ode/src/explicit_runge_kutta.rs b/russell_ode/src/explicit_runge_kutta.rs new file mode 100644 index 00000000..69565f98 --- /dev/null +++ b/russell_ode/src/explicit_runge_kutta.rs @@ -0,0 +1,726 @@ +use crate::constants::*; +use crate::StrError; +use crate::{detect_stiffness, ErkDenseOut, Information, Method, OdeSolverTrait, Params, System, Workspace}; +use russell_lab::{format_fortran, vec_copy, vec_update, Matrix, Vector}; +use russell_sparse::CooMatrix; + +pub(crate) struct ExplicitRungeKutta<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Holds the parameters + params: Params, + + /// ODE system + system: &'a System, + + /// Information such as implicit, embedded, etc. + info: Information, + + /// Runge-Kutta A coefficients + aa: Matrix, + + /// Runge-Kutta B coefficients + bb: Vector, + + /// Runge-Kutta C coefficients + cc: Vector, + + /// (embedded) error coefficients + /// + /// difference between B and Be: e = b - be + ee: Option, + + /// Number of stages + nstage: usize, + + /// Lund stabilization factor (n) + /// + /// `n = 1/(q+1)-0.75⋅β` of `rel_err ⁿ` + lund_factor: f64, + + /// Auxiliary variable: 1 / m_min + d_min: f64, + + /// Auxiliary variable: 1 / m_max + d_max: f64, + + /// Array of vectors holding the updates + /// + /// v[stg][dim] = ya[dim] + h*sum(a[stg][j]*f[j][dim], j, nstage) + v: Vec, + + /// Array of vectors holding the function evaluations + /// + /// k[stg][dim] = f(u[stg], v[stg][dim]) + k: Vec, + + /// Auxiliary workspace (will contain y0 to be used in accept_update) + w: Vector, + + /// Handles the dense output + dense_out: Option, +} + +impl<'a, F, J, A> ExplicitRungeKutta<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Allocates a new instance + pub fn new(params: Params, system: &'a System) -> Result { + // Runge-Kutta coefficients + #[rustfmt::skip] + let (aa, bb, cc) = match params.method { + Method::Radau5 => return Err("cannot use Radau5 with ExplicitRungeKutta"), + Method::BwEuler => return Err("cannot use BwEuler with ExplicitRungeKutta"), + Method::FwEuler => return Err("cannot use FwEuler with ExplicitRungeKutta"), + Method::Rk2 => (Matrix::from(&RUNGE_KUTTA_2_A) , Vector::from(&RUNGE_KUTTA_2_B) , Vector::from(&RUNGE_KUTTA_2_C) ), + Method::Rk3 => (Matrix::from(&RUNGE_KUTTA_3_A) , Vector::from(&RUNGE_KUTTA_3_B) , Vector::from(&RUNGE_KUTTA_3_C) ), + Method::Heun3 => (Matrix::from(&HEUN_3_A) , Vector::from(&HEUN_3_B) , Vector::from(&HEUN_3_C) ), + Method::Rk4 => (Matrix::from(&RUNGE_KUTTA_4_A) , Vector::from(&RUNGE_KUTTA_4_B) , Vector::from(&RUNGE_KUTTA_4_C) ), + Method::Rk4alt => (Matrix::from(&RUNGE_KUTTA_ALT_4_A) , Vector::from(&RUNGE_KUTTA_ALT_4_B) , Vector::from(&RUNGE_KUTTA_ALT_4_C)), + Method::MdEuler => (Matrix::from(&MODIFIED_EULER_A) , Vector::from(&MODIFIED_EULER_B) , Vector::from(&MODIFIED_EULER_C) ), + Method::Merson4 => (Matrix::from(&MERSON_4_A) , Vector::from(&MERSON_4_B) , Vector::from(&MERSON_4_C) ), + Method::Zonneveld4 => (Matrix::from(&ZONNEVELD_4_A) , Vector::from(&ZONNEVELD_4_B) , Vector::from(&ZONNEVELD_4_C) ), + Method::Fehlberg4 => (Matrix::from(&FEHLBERG_4_A) , Vector::from(&FEHLBERG_4_B) , Vector::from(&FEHLBERG_4_C) ), + Method::DoPri5 => (Matrix::from(&DORMAND_PRINCE_5_A) , Vector::from(&DORMAND_PRINCE_5_B) , Vector::from(&DORMAND_PRINCE_5_C) ), + Method::Verner6 => (Matrix::from(&VERNER_6_A) , Vector::from(&VERNER_6_B) , Vector::from(&VERNER_6_C) ), + Method::Fehlberg7 => (Matrix::from(&FEHLBERG_7_A) , Vector::from(&FEHLBERG_7_B) , Vector::from(&FEHLBERG_7_C) ), + Method::DoPri8 => (Matrix::from(&DORMAND_PRINCE_8_A) , Vector::from(&DORMAND_PRINCE_8_B) , Vector::from(&DORMAND_PRINCE_8_C) ), + }; + + // information + let info = params.method.information(); + assert!(!info.implicit); + + // coefficients for error estimate + let ee = if info.embedded { + match params.method { + Method::Radau5 => None, + Method::BwEuler => None, + Method::FwEuler => None, + Method::Rk2 => None, + Method::Rk3 => None, + Method::Heun3 => None, + Method::Rk4 => None, + Method::Rk4alt => None, + Method::MdEuler => Some(Vector::from(&MODIFIED_EULER_E)), + Method::Merson4 => Some(Vector::from(&MERSON_4_E)), + Method::Zonneveld4 => Some(Vector::from(&ZONNEVELD_4_E)), + Method::Fehlberg4 => Some(Vector::from(&FEHLBERG_4_E)), + Method::DoPri5 => Some(Vector::from(&DORMAND_PRINCE_5_E)), + Method::Verner6 => Some(Vector::from(&VERNER_6_E)), + Method::Fehlberg7 => Some(Vector::from(&FEHLBERG_7_E)), + Method::DoPri8 => Some(Vector::from(&DORMAND_PRINCE_8_E)), + } + } else { + None + }; + + // number of stages + let nstage = bb.dim(); + + // Lund stabilization factor (n) + let lund_factor = 1.0 / ((info.order_of_estimator + 1) as f64) - params.erk.lund_beta * params.erk.lund_m; + + // return structure + let ndim = system.ndim; + Ok(ExplicitRungeKutta { + params, + system, + info, + aa, + bb, + cc, + ee, + nstage, + lund_factor, + d_min: 1.0 / params.step.m_min, + d_max: 1.0 / params.step.m_max, + v: vec![Vector::new(ndim); nstage], + k: vec![Vector::new(ndim); nstage], + w: Vector::new(ndim), + dense_out: None, + }) + } +} + +impl<'a, F, J, A> OdeSolverTrait for ExplicitRungeKutta<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Enables dense output + fn enable_dense_output(&mut self) -> Result<(), StrError> { + self.dense_out = Some(ErkDenseOut::new(self.params.method, self.system.ndim)?); + Ok(()) + } + + /// Calculates the quantities required to update x and y + fn step(&mut self, work: &mut Workspace, x: f64, y: &Vector, h: f64, args: &mut A) -> Result<(), StrError> { + // auxiliary + let k = &mut self.k; + let v = &mut self.v; + + // compute k0 (otherwise, use k0 saved in accept) + if (work.bench.n_accepted == 0 || !self.info.first_step_same_as_last) && !work.follows_reject_step { + work.bench.n_function += 1; + (self.system.function)(&mut k[0], x, y, args)?; // k0 := f(x0, y0) + } + + // compute ki + for i in 1..self.nstage { + let ui = x + h * self.cc[i]; + vec_copy(&mut v[i], &y).unwrap(); // vi := ya + for j in 0..i { + vec_update(&mut v[i], h * self.aa.get(i, j), &k[j]).unwrap(); // vi += h ⋅ aij ⋅ kj + } + work.bench.n_function += 1; + (self.system.function)(&mut k[i], ui, &v[i], args)?; // ki := f(ui,vi) + } + + // update (methods without error estimation) + if !self.info.embedded { + for m in 0..self.system.ndim { + self.w[m] = y[m]; + for i in 0..self.nstage { + self.w[m] += self.bb[i] * k[i][m] * h; + } + } + return Ok(()); + } + + // auxiliary + let ee = self.ee.as_ref().unwrap(); + let dim = self.system.ndim as f64; + + // update and error estimation + if self.params.method == Method::DoPri8 { + // Dormand-Prince 8 with 5 and 3 orders + let (bhh1, bhh2, bhh3) = (DORMAND_PRINCE_8_BHH1, DORMAND_PRINCE_8_BHH2, DORMAND_PRINCE_8_BHH3); + let mut err_3 = 0.0; + let mut err_5 = 0.0; + for m in 0..self.system.ndim { + self.w[m] = y[m]; + let mut err_a = 0.0; + let mut err_b = 0.0; + for i in 0..self.nstage { + self.w[m] += self.bb[i] * k[i][m] * h; + err_a += self.bb[i] * k[i][m]; + err_b += ee[i] * k[i][m]; + } + let sk = self.params.tol.abs + self.params.tol.rel * f64::max(f64::abs(y[m]), f64::abs(self.w[m])); + err_a -= bhh1 * k[0][m] + bhh2 * k[8][m] + bhh3 * k[11][m]; + err_3 += (err_a / sk) * (err_a / sk); + err_5 += (err_b / sk) * (err_b / sk); + } + let mut den = err_5 + 0.01 * err_3; // similar to Eq. (10.17) of [1, page 255] + if den <= 0.0 { + den = 1.0; + } + work.rel_error = f64::abs(h) * err_5 * f64::sqrt(1.0 / (dim * den)); + } else { + // all other ERK methods + let mut sum = 0.0; + for m in 0..self.system.ndim { + self.w[m] = y[m]; + let mut err_m = 0.0; + for i in 0..self.nstage { + let kh = k[i][m] * h; + self.w[m] += self.bb[i] * kh; + err_m += ee[i] * kh; + } + let sk = self.params.tol.abs + self.params.tol.rel * f64::max(f64::abs(y[m]), f64::abs(self.w[m])); + let ratio = err_m / sk; + sum += ratio * ratio; + } + work.rel_error = f64::max(f64::sqrt(sum / dim), 1.0e-10); + } + Ok(()) + } + + /// Updates x and y and computes the next stepsize + fn accept( + &mut self, + work: &mut Workspace, + x: &mut f64, + y: &mut Vector, + h: f64, + args: &mut A, + ) -> Result<(), StrError> { + // save data for dense output + if let Some(out) = self.dense_out.as_mut() { + work.bench.n_function += out.update(&mut self.system, *x, y, h, &self.w, &self.k, args)?; + } + + // update x and y + *x += h; + vec_copy(y, &self.w).unwrap(); + + // update k0 + if self.info.first_step_same_as_last { + for m in 0..self.system.ndim { + self.k[0][m] = self.k[self.nstage - 1][m]; // k0 := ks for next step + } + } + + // exit if not embedded method + if !self.info.embedded { + return Ok(()); + } + + // estimate the new stepsize + let mut fac = f64::powf(work.rel_error, self.lund_factor); // line 463 of dopri5.f + if self.params.erk.lund_beta > 0.0 && work.rel_error_prev > 0.0 { + // lund-stabilization (line 465 of dopri5.f) + fac = fac / f64::powf(work.rel_error_prev, self.params.erk.lund_beta); + } + fac = f64::max(self.d_max, f64::min(self.d_min, fac / self.params.step.m_safety)); // line 467 of dopri5.f + work.h_new = h / fac; + + // stiffness detection + if self.params.stiffness.enabled { + if self.params.method == Method::DoPri5 { + let mut num = 0.0; + let mut den = 0.0; + for m in 0..self.system.ndim { + let delta_k = self.k[6][m] - self.k[5][m]; // k7 - k6 (Eq 2.26, HW-PartII, page 22) + let delta_v = self.v[6][m] - self.v[5][m]; // v7 - v6 (Eq 2.26, HW-PartII, page 22) + num += delta_k * delta_k; + den += delta_v * delta_v; + } + if den > f64::EPSILON { + work.stiff_h_times_lambda = h * f64::sqrt(num / den); + } + detect_stiffness(work, &self.params)?; + } else if self.params.method == Method::DoPri8 { + const NEW: usize = 10; // to use k[NEW] as a temporary workspace + work.bench.n_function += 1; + (self.system.function)(&mut self.k[NEW], *x, y, args)?; // line 663 of dop853.f + let mut num = 0.0; + let mut den = 0.0; + for m in 0..self.system.ndim { + let delta_k = self.k[NEW][m] - self.k[11][m]; // line 670 of dop843.f + let delta_v = y[m] - self.v[11][m]; + num += delta_k * delta_k; + den += delta_v * delta_v; + } + if den > f64::EPSILON { + work.stiff_h_times_lambda = h * f64::sqrt(num / den); + } + detect_stiffness(work, &self.params)?; + } + }; + + // print debug messages + if self.params.debug { + if work.stiff_detected { + println!( + "THE PROBLEM SEEMS TO BECOME STIFF AT X ={}, ACCEPTED STEP ={:>5}", + format_fortran(*x - h), + work.bench.n_accepted + ); + } + println!( + "step(A) ={:>5}, err ={}, h_new ={}, n_yes ={:>4}, n_no ={:>4}, h*lambda ={}", + work.bench.n_steps, + format_fortran(work.rel_error), + format_fortran(work.h_new), + work.stiff_n_detection_yes, + work.stiff_n_detection_no, + format_fortran(work.stiff_h_times_lambda), + ); + } + Ok(()) + } + + /// Rejects the update + fn reject(&mut self, work: &mut Workspace, h: f64) { + // estimate new stepsize + let d = f64::powf(work.rel_error, self.lund_factor) / self.params.step.m_safety; + work.h_new = h / f64::min(self.d_min, d); + + // print debug messages + if self.params.debug { + println!( + "step(R) ={:>5}, err ={}, h_new ={}", + work.bench.n_steps, + format_fortran(work.rel_error), + format_fortran(work.h_new), + ); + } + } + + /// Computes the dense output with x-h ≤ x_out ≤ x + fn dense_output(&self, y_out: &mut Vector, x_out: f64, x: f64, _y: &Vector, h: f64) { + if let Some(out) = self.dense_out.as_ref() { + out.calculate(y_out, x_out, x, h); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::ExplicitRungeKutta; + use crate::{no_jacobian, HasJacobian, Method, OdeSolverTrait, Params, Samples, System, Workspace}; + use russell_lab::{approx_eq, vec_approx_eq, Vector}; + + #[test] + fn constants_are_consistent() { + let methods = Method::explicit_methods(); + let staged = methods.iter().filter(|&&m| m != Method::FwEuler); + struct Args {} + for method in staged { + println!("\n... {:?} ...", method); + let params = Params::new(*method); + let system = System::new( + 1, + |_, _, _, _args: &mut Args| Ok(()), + no_jacobian, + HasJacobian::No, + None, + None, + ); + let erk = ExplicitRungeKutta::new(params, &system).unwrap(); + let nstage = erk.nstage; + assert_eq!(erk.aa.dims(), (nstage, nstage)); + assert_eq!(erk.bb.dim(), nstage); + assert_eq!(erk.cc.dim(), nstage); + let info = method.information(); + if info.embedded { + let ee = erk.ee.as_ref().unwrap(); + assert_eq!(ee.dim(), nstage); + } + assert_eq!(erk.cc[0], 0.0); // all C[0] are zero + + println!("Σi bi = 1 (Eq. 1.11a, page 135)"); + let mut sum = 0.0; + for i in 0..nstage { + sum += erk.bb[i]; + } + approx_eq(sum, 1.0, 1e-15); + + println!("Σi bi ci = 1/2 (Eq. 1.11b, page 135)"); + sum = 0.0; + for i in 0..nstage { + sum += erk.bb[i] * erk.cc[i]; + } + approx_eq(sum, 1.0 / 2.0, 1e-15); + + if erk.info.order < 4 { + continue; + } + + println!("Σi bi ci² = 1/3 (Eq. 1.11c, page 135)"); + sum = 0.0; + for i in 0..nstage { + sum += erk.bb[i] * erk.cc[i] * erk.cc[i]; + } + approx_eq(sum, 1.0 / 3.0, 1e-15); + + println!("Σi,j bi aij cj = 1/6 (Eq. 1.11d, page 135)"); + sum = 0.0; + for i in 0..nstage { + for j in 0..nstage { + sum += erk.bb[i] * erk.aa.get(i, j) * erk.cc[j]; + } + } + approx_eq(sum, 1.0 / 6.0, 1e-15); + + println!("Σi bi ci³ = 1/4 (Eq. 1.11e, page 135)"); + sum = 0.0; + for i in 0..nstage { + sum += erk.bb[i] * erk.cc[i] * erk.cc[i] * erk.cc[i]; + } + approx_eq(sum, 1.0 / 4.0, 1e-15); + + println!("Σi,j bi ci aij cj = 1/8 (Eq. 1.11f, page 135)"); + sum = 0.0; + for i in 0..nstage { + for j in 0..nstage { + sum += erk.bb[i] * erk.cc[i] * erk.aa.get(i, j) * erk.cc[j]; + } + } + approx_eq(sum, 1.0 / 8.0, 1e-15); + + println!("Σi,j bi aij cj² = 1/12 (Eq. 1.11g, page 136)"); + sum = 0.0; + for i in 0..nstage { + for j in 0..nstage { + sum += erk.bb[i] * erk.aa.get(i, j) * erk.cc[j] * erk.cc[j]; + } + } + approx_eq(sum, 1.0 / 12.0, 1e-15); + + println!("Σi,j,k bi aij ajk ck = 1/24 (Eq. 1.11h, page 136)"); + sum = 0.0; + for i in 0..nstage { + for j in 0..nstage { + for k in 0..nstage { + sum += erk.bb[i] * erk.aa.get(i, j) * erk.aa.get(j, k) * erk.cc[k]; + } + } + } + approx_eq(sum, 1.0 / 24.0, 1e-15); + + println!("Σ(i=1,s) bi ciⁿ⁻¹ = 1 / n for n = 1..8 (Eq. 5.20a, page 181)"); + for n in 1..erk.info.order { + let mut sum = 0.0; + for i in 1..(nstage + 1) { + sum += erk.bb[i - 1] * f64::powf(erk.cc[i - 1], (n - 1) as f64); + } + approx_eq(sum, 1.0 / (n as f64), 1e-15); + } + + println!("Σ(j=1,i-1) aij = ci for i = 1..s (Eq. 5.20b, page 181)"); + for i in 1..(nstage + 1) { + let mut sum = 0.0; + for j in 1..i { + sum += erk.aa.get(i - 1, j - 1); + } + approx_eq(sum, erk.cc[i - 1], 1e-14); + } + + if erk.info.order < 5 { + continue; + } + + println!("Σ(j=1,i-1) aij cj = ci² / 2 for i = 3..s (Eq. 5.20c, page 181)"); + for i in 3..(nstage + 1) { + let mut sum = 0.0; + for j in 1..i { + sum += erk.aa.get(i - 1, j - 1) * erk.cc[j - 1]; + } + approx_eq(sum, erk.cc[i - 1] * erk.cc[i - 1] / 2.0, 1e-14); + } + } + } + + #[test] + fn modified_euler_works() { + // This test relates to Table 21.2 of Kreyszig's book, page 904 + + // problem + let (system, data, mut args) = Samples::kreyszig_eq6_page902(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let params = Params::new(Method::MdEuler); // aka the Improved Euler in Kreyszig's book + let mut solver = ExplicitRungeKutta::new(params, &system).unwrap(); + let mut work = Workspace::new(Method::FwEuler); + + // check dense output availability + assert_eq!( + solver.enable_dense_output().err(), + Some("dense output is not available for the MdEuler method") + ); + + // numerical approximation + let h = 0.2; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + yfx(&mut y_ana, x); + let mut xx = vec![x]; + let mut yy_num = vec![y[0]]; + let mut yy_ana = vec![y_ana[0]]; + let mut errors = vec![f64::abs(yy_num[0] - yy_ana[0])]; + for n in 0..5 { + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + assert_eq!(work.bench.n_function, (n + 1) * 2); + + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + xx.push(x); + yy_num.push(y[0]); + + yfx(&mut y_ana, x); + yy_ana.push(y_ana[0]); + errors.push(f64::abs(yy_num.last().unwrap() - yy_ana.last().unwrap())); + } + + // Mathematica code: + // + // MdEulerSingleEq[f_, x0_, y0_, x1_, h_] := Module[{x, y, nstep, k1, k2}, + // x[1] = x0; + // y[1] = y0; + // nstep = IntegerPart[(x1 - x0)/h] + 1; + // Do[ + // k1 = f[x[i], y[i]]; + // k2 = f[x[i] + h, y[i] + h k1]; + // x[i + 1] = x[i] + h; + // y[i + 1] = y[i] + h/2 (k1 + k2); + // , {i, 1, nstep}]; + // Table[{x[i], y[i]}, {i, 1, nstep}] + // ]; + // + // f[x_, y_] := x + y; + // x0 = 0; y0 = 0; x1 = 1; h = 0.2; + // xy = MdEulerSingleEq[f, x0, y0, x1, h]; + // err = Abs[#[[2]] - (Exp[#[[1]]] - #[[1]] - 1)] & /@ xy; + // + // Print["x = ", NumberForm[xy[[All, 1]], 20]] + // Print["y = ", NumberForm[xy[[All, 2]], 20]] + // Print["err = ", NumberForm[err, 20]] + + // compare with Mathematica results + let xx_correct = &[0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; + let yy_correct = &[0.0, 0.02, 0.0884, 0.215848, 0.41533456, 0.7027081632000001]; + let errors_correct = &[ + 0.0, + 0.001402758160169895, + 0.00342469764127043, + 0.006270800390509007, + 0.01020636849246781, + 0.01557366525904502, + ]; + vec_approx_eq(&xx, xx_correct, 1e-15); + vec_approx_eq(&yy_num, yy_correct, 1e-15); + vec_approx_eq(&errors, errors_correct, 1e-15); + } + + #[test] + fn rk4_works() { + // This test relates to Table 21.4 of Kreyszig's book, page 904 + + // problem + let (system, data, mut args) = Samples::kreyszig_eq6_page902(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let params = Params::new(Method::Rk4); // aka the Classical RK in Kreyszig's book + let mut solver = ExplicitRungeKutta::new(params, &system).unwrap(); + let mut work = Workspace::new(Method::FwEuler); + + // check dense output availability + assert_eq!( + solver.enable_dense_output().err(), + Some("dense output is not available for the Rk4 method") + ); + + // numerical approximation + let h = 0.2; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + yfx(&mut y_ana, x); + let mut xx = vec![x]; + let mut yy_num = vec![y[0]]; + let mut yy_ana = vec![y_ana[0]]; + let mut errors = vec![f64::abs(yy_num[0] - yy_ana[0])]; + for n in 0..5 { + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + assert_eq!(work.bench.n_function, (n + 1) * 4); + + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + xx.push(x); + yy_num.push(y[0]); + + yfx(&mut y_ana, x); + yy_ana.push(y_ana[0]); + errors.push(f64::abs(yy_num.last().unwrap() - yy_ana.last().unwrap())); + } + + // Mathematica code: + // + // RK4SingleEq[f_, x0_, y0_, x1_, h_] := Module[{x, y, nstep, k1, k2, k3, k4}, + // x[1] = x0; + // y[1] = y0; + // nstep = IntegerPart[(x1 - x0)/h] + 1; + // Do[ + // k1 = f[x[i], y[i]]; + // k2 = f[x[i] + 1/2 h, y[i] + h/2 k1]; + // k3 = f[x[i] + 1/2 h, y[i] + h/2 k2]; + // k4 = f[x[i] + h, y[i] + h k3]; + // x[i + 1] = x[i] + h; + // y[i + 1] = y[i] + h/6 (k1 + 2 k2 + 2 k3 + k4); + // , {i, 1, nstep}]; + // Table[{x[i], y[i]}, {i, 1, nstep}] + // ]; + // + // f[x_, y_] := x + y; + // x0 = 0; y0 = 0; x1 = 1; h = 0.2; + // xy = RK4SingleEq[f, x0, y0, x1, h]; + // err = Abs[#[[2]] - (Exp[#[[1]]] - #[[1]] - 1)] & /@ xy; + // + // Print["x = ", NumberForm[xy[[All, 1]], 20]] + // Print["y = ", NumberForm[xy[[All, 2]], 20]] + // Print["err = ", NumberForm[err, 20]] + + // compare with Mathematica results + let xx_correct = &[0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; + let yy_correct = &[ + 0.0, + 0.0214, + 0.09181796, + 0.222106456344, + 0.4255208257785617, + 0.7182511366059352, + ]; + let errors_correct = &[ + 0.0, + 2.758160169896717e-6, + 6.737641270432304e-6, + 0.00001234404650901633, + 0.00002010271390617824, + 0.00003069185310988765, + ]; + vec_approx_eq(&xx, xx_correct, 1e-15); + vec_approx_eq(&yy_num, yy_correct, 1e-15); + vec_approx_eq(&errors, errors_correct, 1e-15); + } + + #[test] + fn fehlberg4_step_works() { + // Solving Equation (11) from Kreyszig's page 908 - Example 3 + // + // ```text + // dy + // —— = (y - x - 1)² + 2 with y(x=0)=1 + // dx + // + // y(x) = tan(x) + x + 1 + // ``` + let system = System::new( + 1, + |f, x, y, _: &mut u8| { + let d = y[0] - x - 1.0; + f[0] = d * d + 2.0; + Ok(()) + }, + no_jacobian, + HasJacobian::No, + None, + None, + ); + + // allocate solver + let params = Params::new(Method::Fehlberg4); + let mut solver = ExplicitRungeKutta::new(params, &system).unwrap(); + let mut work = Workspace::new(Method::FwEuler); + + // perform one step (compute k) + let x = 0.0; + let y = Vector::from(&[1.0]); + let h = 0.1; + let mut args = 0; + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + + // compare with Kreyszig's results (Example 3, page 908) + let kh: Vec<_> = solver.k.iter().map(|k| k[0] * h).collect(); + let kh_correct = &[ + 0.200000000000, + 0.200062500000, + 0.200140756867, + 0.200856926154, + 0.201006676700, + 0.200250418651, + ]; + vec_approx_eq(&kh, kh_correct, 1e-12); + } +} diff --git a/russell_ode/src/lib.rs b/russell_ode/src/lib.rs new file mode 100644 index 00000000..7b1f1d05 --- /dev/null +++ b/russell_ode/src/lib.rs @@ -0,0 +1,52 @@ +//! Russell - Rust Scientific Library +//! +//! `russell_ode`: Solvers for Ordinary Differential Equations + +/// Defines the error output as a static string +pub type StrError = &'static str; + +mod benchmark; +mod constants; +mod detect_stiffness; +mod enums; +mod erk_dense_out; +mod euler_backward; +mod euler_forward; +mod explicit_runge_kutta; +mod ode_solver; +mod ode_solver_trait; +mod output; +mod params; +pub mod prelude; +mod radau5; +mod samples; +mod system; +mod workspace; +pub use crate::benchmark::*; +pub use crate::constants::*; +use crate::detect_stiffness::*; +pub use crate::enums::*; +use crate::erk_dense_out::*; +use crate::euler_backward::*; +use crate::euler_forward::*; +use crate::explicit_runge_kutta::*; +pub use crate::ode_solver::*; +use crate::ode_solver_trait::*; +pub use crate::output::*; +pub use crate::params::*; +use crate::radau5::*; +pub use crate::samples::*; +pub use crate::system::*; +use crate::workspace::*; + +// run code from README file +#[cfg(doctest)] +mod test_readme { + macro_rules! external_doc_test { + ($x:expr) => { + #[doc = $x] + extern "C" {} + }; + } + external_doc_test!(include_str!("../README.md")); +} diff --git a/russell_ode/src/ode_solver.rs b/russell_ode/src/ode_solver.rs new file mode 100644 index 00000000..95b29ec8 --- /dev/null +++ b/russell_ode/src/ode_solver.rs @@ -0,0 +1,477 @@ +use crate::constants::N_EQUAL_STEPS; +use crate::{Benchmark, Method, OdeSolverTrait, Params, System, Workspace}; +use crate::{EulerBackward, EulerForward, ExplicitRungeKutta, Radau5}; +use crate::{Output, StrError}; +use russell_lab::Vector; +use russell_sparse::CooMatrix; + +/// Implements a numerical solver for systems of ODEs +/// +/// The system is defined by: +/// +/// ```text +/// d{y} +/// ———— = {f}(x, {y}) +/// dx +/// where x is a scalar and {y} and {f} are vectors +/// ``` +/// +/// The Jacobian is defined by: +/// +/// ```text +/// ∂{f} +/// [J](x, {y}) = ———— +/// ∂{y} +/// where [J] is the Jacobian matrix +/// ``` +pub struct OdeSolver<'a, A> { + /// Holds the parameters + params: Params, + + /// Dimension of the ODE system + ndim: usize, + + /// Holds a pointer to the actual ODE system solver + actual: Box + 'a>, + + /// Holds benchmark and work variables + work: Workspace, +} + +impl<'a, A> OdeSolver<'a, A> { + /// Allocates a new instance + /// + /// # Input + /// + /// * `params` -- holds all parameters, including the selection of the numerical [Method] + /// * `system` -- defines the ODE system + /// + /// # Generics + /// + /// See [System] for an explanation of the generic parameters. + pub fn new(params: Params, system: &'a System) -> Result + where + F: 'a + Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: 'a + Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, + A: 'a, + { + params.validate()?; + let ndim = system.ndim; + let actual: Box> = if params.method == Method::Radau5 { + Box::new(Radau5::new(params, system)) + } else if params.method == Method::BwEuler { + Box::new(EulerBackward::new(params, system)) + } else if params.method == Method::FwEuler { + Box::new(EulerForward::new(system)) + } else { + Box::new(ExplicitRungeKutta::new(params, system)?) + }; + Ok(OdeSolver { + params, + ndim, + actual, + work: Workspace::new(params.method), + }) + } + + /// Returns some benchmarking data + pub fn bench(&self) -> &Benchmark { + &self.work.bench + } + + /// Solves the ODE system + /// + /// # Input + /// + /// * `y0` -- the initial value of the vector of dependent variables; it will be updated to `y1` at the end + /// * `x0` -- the initial value of the independent variable + /// * `x1` -- the final value of the independent variable + /// * `h_equal` -- a constant stepsize for solving with equal-steps; otherwise, + /// if possible, variable step sizes are automatically calculated. If automatic + /// stepping is not possible (e.g., the RK method is not embedded), + /// a constant (and equal) stepsize will be calculated for [N_EQUAL_STEPS] steps. + /// * `output` -- structure to hold the results at accepted steps or at specified stations (continuous/dense output) + pub fn solve( + &mut self, + y0: &mut Vector, + x0: f64, + x1: f64, + h_equal: Option, + mut output: Option<&mut Output>, + args: &mut A, + ) -> Result<(), StrError> { + // check data + if y0.dim() != self.ndim { + return Err("y0.dim() must be equal to ndim"); + } + if x1 < x0 { + return Err("x1 must be greater than x0"); + } + + // information + let info = self.params.method.information(); + + // initial stepsize + let (equal_stepping, mut h) = match h_equal { + Some(h_eq) => { + if h_eq < 10.0 * f64::EPSILON { + return Err("h_equal must be ≥ 10.0 * f64::EPSILON"); + } + let n = f64::ceil((x1 - x0) / h_eq) as usize; + let h = (x1 - x0) / (n as f64); + (true, h) + } + None => { + if info.embedded { + let h = f64::min(self.params.step.h_ini, x1 - x0); + (false, h) + } else { + let h = (x1 - x0) / (N_EQUAL_STEPS as f64); + (true, h) + } + } + }; + assert!(h > 0.0); + + // reset variables + self.work.reset(h, self.params.step.rel_error_prev_min); + + // current values + let mut x = x0; // will become x1 at the end + let y = y0; // will become y1 at the end + + // first output + if let Some(out) = output.as_mut() { + if out.with_dense_output() { + self.actual.enable_dense_output()?; + } + out.save_stiff = self.params.stiffness.save_results; + out.push(&self.work, x, y, h, &self.actual)?; + } + + // equal-stepping loop + if equal_stepping { + let nstep = f64::ceil((x1 - x) / h) as usize; + for _ in 0..nstep { + self.work.bench.sw_step.reset(); + + // step + self.work.bench.n_steps += 1; + self.actual.step(&mut self.work, x, &y, h, args)?; + + // update x and y + self.work.bench.n_accepted += 1; // this must be after `self.actual.step` + self.actual.accept(&mut self.work, &mut x, y, h, args)?; + + // output + if let Some(out) = output.as_mut() { + out.push(&self.work, x, y, h, &self.actual)?; + } + self.work.bench.stop_sw_step(); + } + self.work.bench.stop_sw_total(); + return Ok(()); + } + + // variable steps: control variables + let mut success = false; + let mut last_step = false; + + // variable stepping loop + for _ in 0..self.params.step.n_step_max { + self.work.bench.sw_step.reset(); + + // converged? + let dx = x1 - x; + if dx <= 10.0 * f64::EPSILON { + success = true; + self.work.bench.stop_sw_step(); + break; + } + + // update and check the stepsize + h = f64::min(self.work.h_new, dx); + if h <= 10.0 * f64::EPSILON { + return Err("the stepsize becomes too small"); + } + + // step + self.work.bench.n_steps += 1; + self.actual.step(&mut self.work, x, &y, h, args)?; + + // handle diverging iterations + if self.work.iterations_diverging { + self.work.iterations_diverging = false; + self.work.follows_reject_step = true; + last_step = false; + self.work.h_new = h * self.work.h_multiplier_diverging; + continue; + } + + // accept step + if self.work.rel_error < 1.0 { + // update x and y + self.work.bench.n_accepted += 1; + self.actual.accept(&mut self.work, &mut x, y, h, args)?; + + // do not allow h to grow if previous step was a reject + if self.work.follows_reject_step { + self.work.h_new = f64::min(self.work.h_new, h); + } + self.work.follows_reject_step = false; + + // save previous stepsize, relative error, and accepted/suggested stepsize + self.work.h_prev = h; + self.work.rel_error_prev = f64::max(self.params.step.rel_error_prev_min, self.work.rel_error); + self.work.bench.h_accepted = self.work.h_new; + + // output + if let Some(out) = output.as_mut() { + out.push(&self.work, x, y, h, &self.actual)?; + } + + // converged? + if last_step { + success = true; + self.work.bench.stop_sw_step(); + break; + } + + // check if the last step is approaching + if x + self.work.h_new >= x1 { + last_step = true; + } + + // reject step + } else { + // set flags + if self.work.bench.n_accepted > 0 { + self.work.bench.n_rejected += 1; + } + self.work.follows_reject_step = true; + last_step = false; + + // recompute stepsize + if self.work.bench.n_accepted == 0 && self.params.step.m_first_reject > 0.0 { + self.work.h_new = h * self.params.step.m_first_reject; + } else { + self.actual.reject(&mut self.work, h); + } + } + } + + // done + self.work.bench.stop_sw_total(); + if success { + if f64::abs(x - x1) > 10.0 * f64::EPSILON { + return Err("x is not equal to x1 at the end"); + } + Ok(()) + } else { + Err("variable stepping did not converge") + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::OdeSolver; + use crate::{Method, Output, Params, Samples, N_EQUAL_STEPS}; + use russell_lab::{vec_approx_eq, vec_copy, Vector}; + + #[test] + fn solve_with_step_output_works() { + // ODE system + let (system, data, mut args) = Samples::simple_equation_constant(); + + // output + let mut out = Output::new(); + out.y_analytical = data.y_analytical; + out.enable_step(&[0]); + + // params and solver + let params = Params::new(Method::FwEuler); + let mut solver = OdeSolver::new(params, &system).unwrap(); + + // solve the ODE system (will run with N_EQUAL_STEPS) + let mut y = data.y0.clone(); + solver + .solve(&mut y, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // check + let h_equal_correct = (data.x1 - data.x0) / (N_EQUAL_STEPS as f64); + let h_values_correct = Vector::filled(N_EQUAL_STEPS + 1, h_equal_correct); + let x_values_correct = Vector::linspace(data.x0, data.x1, N_EQUAL_STEPS + 1).unwrap(); + let e_values_correct = Vector::new(N_EQUAL_STEPS + 1); // all 0.0 + vec_approx_eq(y.as_data(), &[1.0], 1e-15); + vec_approx_eq(&out.step_h, h_values_correct.as_data(), 1e-15); + vec_approx_eq(&out.step_x, x_values_correct.as_data(), 1e-15); + vec_approx_eq(&out.step_y.get(&0).unwrap(), x_values_correct.as_data(), 1e-15); + vec_approx_eq(&out.step_global_error, e_values_correct.as_data(), 1e-15); + + // run again without step output + out.clear(); + out.disable_step(); + vec_copy(&mut y, &data.y0).unwrap(); + solver + .solve(&mut y, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + vec_approx_eq(y.as_data(), &[1.0], 1e-15); + assert_eq!(out.step_h.len(), 0); + assert_eq!(out.step_x.len(), 0); + assert_eq!(out.step_y.get(&0).unwrap().len(), 0); + assert_eq!(out.step_global_error.len(), 0); + } + + #[test] + fn solve_with_h_equal_works() { + // ODE system + let (system, mut data, mut args) = Samples::simple_equation_constant(); + + // output + let mut out = Output::new(); + out.enable_step(&[0]); + + // params and solver + let params = Params::new(Method::FwEuler); + let mut solver = OdeSolver::new(params, &system).unwrap(); + let x1 = 1.2; // => will generate 4 steps + + // capture error + let h_equal = Some(f64::EPSILON); // will cause error + assert_eq!( + solver + .solve(&mut data.y0, data.x0, x1, h_equal, Some(&mut out), &mut args) + .err(), + Some("h_equal must be ≥ 10.0 * f64::EPSILON") + ); + + // solve the ODE system + let h_equal = Some(0.3); + solver + .solve(&mut data.y0, data.x0, x1, h_equal, Some(&mut out), &mut args) + .unwrap(); + + // check + let nstep = 4; + let h_values_correct = Vector::filled(nstep + 1, 0.3); + let x_values_correct = Vector::linspace(data.x0, x1, nstep + 1).unwrap(); + vec_approx_eq(data.y0.as_data(), &[x1], 1e-15); + vec_approx_eq(&out.step_h, h_values_correct.as_data(), 1e-15); + vec_approx_eq(&out.step_x, x_values_correct.as_data(), 1e-15); + vec_approx_eq(&out.step_y.get(&0).unwrap(), x_values_correct.as_data(), 1e-15); + assert_eq!(out.step_global_error.len(), 0); // not available when y_analytical is not provided + } + + #[test] + fn solve_with_variable_steps_works() { + // ODE system + let (system, mut data, mut args) = Samples::simple_equation_constant(); + + // output + let mut out = Output::new(); + out.y_analytical = data.y_analytical; + out.enable_step(&[0]); + + // params and solver + let mut params = Params::new(Method::MdEuler); + params.step.h_ini = 0.1; + let mut solver = OdeSolver::new(params, &system).unwrap(); + + // solve the ODE system + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // check + vec_approx_eq(data.y0.as_data(), &[1.0], 1e-15); + vec_approx_eq(&out.step_h, &[0.1, 0.1, 0.9], 1e-15); + vec_approx_eq(&out.step_x, &[0.0, 0.1, 1.0], 1e-15); + vec_approx_eq(&out.step_y.get(&0).unwrap(), &[0.0, 0.1, 1.0], 1e-15); + vec_approx_eq(&&out.step_global_error, &[0.0, 0.0, 0.0], 1e-15); + } + + #[test] + fn solve_with_dense_output_works() { + // ODE system + let (system, data, mut args) = Samples::simple_equation_constant(); + + // output + let mut out = Output::new(); + out.enable_dense(0.25, &[0]).unwrap(); + + // params and solver + let mut params = Params::new(Method::DoPri5); + params.step.h_ini = 0.1; + let mut solver = OdeSolver::new(params, &system).unwrap(); + + // solve the ODE system + let mut y = data.y0.clone(); + solver + .solve(&mut y, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // check + vec_approx_eq(y.as_data(), &[1.0], 1e-15); + vec_approx_eq(&out.dense_x, &[0.0, 0.25, 0.5, 0.75], 1e-15); // will not store the last x here + vec_approx_eq(&out.dense_y.get(&0).unwrap(), &[0.0, 0.25, 0.5, 0.75], 1e-15); + assert_eq!(&out.dense_step_index, &[0, 2, 2, 2]); + + // run again without dense output + out.clear(); + out.disable_dense(); + vec_copy(&mut y, &data.y0).unwrap(); + solver + .solve(&mut y, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + vec_approx_eq(y.as_data(), &[1.0], 1e-15); + assert_eq!(out.dense_x.len(), 0); + assert_eq!(out.dense_y.get(&0).unwrap().len(), 0); + assert_eq!(out.dense_step_index.len(), 0); + } + + #[test] + fn solve_handles_zero_stepsize() { + // ODE system + let (system, mut data, mut args) = Samples::simple_equation_constant(); + + // output + let mut out = Output::new(); + out.enable_step(&[0]); + + // params and solver + let mut params = Params::new(Method::DoPri5); + params.step.h_ini = 20.0; // since the problem is linear, this stepsize will lead to a jump from x=0.0 to 1.0 + let mut solver = OdeSolver::new(params, &system).unwrap(); + + // solve the ODE system + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // check + vec_approx_eq(data.y0.as_data(), &[1.0], 1e-15); + vec_approx_eq(&out.step_h, &[1.0, 1.0], 1e-15); + vec_approx_eq(&out.step_x, &[0.0, 1.0], 1e-15); + vec_approx_eq(&out.step_y.get(&0).unwrap(), &[0.0, 1.0], 1e-15); + } + + #[test] + fn solve_and_output_handle_errors() { + let (system, mut data, mut args) = Samples::simple_equation_constant(); + let params = Params::new(Method::FwEuler); + let mut solver = OdeSolver::new(params, &system).unwrap(); + let mut out = Output::new(); + assert_eq!(out.enable_dense(-0.1, &[0]).err(), Some("h_out must be positive")); + out.enable_dense(0.1, &[0]).unwrap(); + assert_eq!( + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .err(), + Some("dense output is not available for the FwEuler method") + ); + } +} diff --git a/russell_ode/src/ode_solver_trait.rs b/russell_ode/src/ode_solver_trait.rs new file mode 100644 index 00000000..df79cd05 --- /dev/null +++ b/russell_ode/src/ode_solver_trait.rs @@ -0,0 +1,28 @@ +use crate::StrError; +use crate::Workspace; +use russell_lab::Vector; + +/// Defines the numerical solver +pub(crate) trait OdeSolverTrait { + /// Enables dense output + fn enable_dense_output(&mut self) -> Result<(), StrError>; + + /// Calculates the quantities required to update x and y + fn step(&mut self, work: &mut Workspace, x: f64, y: &Vector, h: f64, args: &mut A) -> Result<(), StrError>; + + /// Updates x and y and computes the next stepsize + fn accept( + &mut self, + work: &mut Workspace, + x: &mut f64, + y: &mut Vector, + h: f64, + args: &mut A, + ) -> Result<(), StrError>; + + /// Rejects the update + fn reject(&mut self, work: &mut Workspace, h: f64); + + /// Computes the dense output with x-h ≤ x_out ≤ x + fn dense_output(&self, y_out: &mut Vector, x_out: f64, x: f64, y: &Vector, h: f64); +} diff --git a/russell_ode/src/output.rs b/russell_ode/src/output.rs new file mode 100644 index 00000000..3334bea5 --- /dev/null +++ b/russell_ode/src/output.rs @@ -0,0 +1,211 @@ +use crate::StrError; +use crate::{OdeSolverTrait, Workspace}; +use russell_lab::{vec_max_abs_diff, Vector}; +use std::collections::HashMap; + +/// Holds the (x,y) results at accepted steps or interpolated within a "dense" sequence +pub struct Output { + /// Indicates whether the accepted step output is to be performed or not + save_step: bool, + + /// Holds the stepsize output during accepted steps + pub step_h: Vec, + + /// Holds the x values output during accepted steps + pub step_x: Vec, + + /// Holds the selected y components output during accepted steps + pub step_y: HashMap>, + + /// Holds the global error output during accepted steps (if the analytical solution is available) + /// + /// The global error is the maximum absolute difference between `y_numerical` and `y_analytical` (see [russell_lab::vec_max_abs_diff]) + pub step_global_error: Vec, + + /// Holds the stepsize to perform the dense output (None means disabled) + dense_h: Option, + + /// Holds the indices of the accepted steps that were used to compute the dense output + pub dense_step_index: Vec, + + /// Holds the x values requested by the dense output + pub dense_x: Vec, + + /// Holds the selected y components requested by the dense output + pub dense_y: HashMap>, + + /// Saves the stations where stiffness has been detected + pub(crate) save_stiff: bool, + + /// Holds the indices of the accepted steps where stiffness has been detected + pub stiff_step_index: Vec, + + /// Holds the x stations where stiffness has been detected + pub stiff_x: Vec, + + /// Holds an auxiliary y vector (e.g., to compute the analytical solution or the dense output) + y_aux: Vector, + + /// Holds a function to compute the analytical solution y(x) + pub y_analytical: Option, +} + +impl Output { + /// Allocates a new instance + pub fn new() -> Self { + const EMPTY: usize = 0; + Output { + save_step: false, + step_h: Vec::new(), + step_x: Vec::new(), + step_y: HashMap::new(), + step_global_error: Vec::new(), + dense_h: None, + dense_step_index: Vec::new(), + dense_x: Vec::new(), + dense_y: HashMap::new(), + save_stiff: false, + stiff_step_index: Vec::new(), + stiff_x: Vec::new(), + y_aux: Vector::new(EMPTY), + y_analytical: None, + } + } + + /// Enables saving the results at accepted steps + /// + /// # Input + /// + /// * `selected_y_components` -- Specifies which components of the `y` vector are to be saved + /// + /// # Results + /// + /// * The results will be saved in the `step_h`, `step_x`, and `step_y` arrays + /// * If the analytical solution is provided, the global error will be saved in the `step_global_error` array + /// * The global error is the maximum absolute difference between `y_numerical` and `y_analytical` (see [russell_lab::vec_max_abs_diff]) + pub fn enable_step(&mut self, selected_y_components: &[usize]) -> &mut Self { + self.save_step = true; + for m in selected_y_components { + self.step_y.insert(*m, Vec::new()); + } + self + } + + /// Disables saving the results at accepted steps + pub fn disable_step(&mut self) -> &mut Self { + self.save_step = false; + self + } + + /// Enables saving the results at a predefined "dense" sequence of steps + /// + /// # Input + /// + /// * `h_out` -- is the stepsize (possibly different than the actual `h` stepsize) for the equally spaced "dense" results + /// * `selected_y_components` -- Specifies which components of the `y` vector are to be saved + /// + /// # Results + /// + /// * The results will be saved in the `dense_x` and `dense_y` arrays + /// * The indices of the associated accepted step will be saved in the `dense_step_index` array + pub fn enable_dense(&mut self, h_out: f64, selected_y_components: &[usize]) -> Result<&mut Self, StrError> { + if h_out < 0.0 { + return Err("h_out must be positive"); + } + self.dense_h = Some(h_out); + for m in selected_y_components { + self.dense_y.insert(*m, Vec::new()); + } + Ok(self) + } + + /// Disables saving the results at a predefined "dense" sequence of steps + pub fn disable_dense(&mut self) -> &mut Self { + self.dense_h = None; + self + } + + /// Indicates whether dense output is enabled or not + pub(crate) fn with_dense_output(&self) -> bool { + self.dense_h.is_some() + } + + /// Clears all resulting arrays + pub fn clear(&mut self) { + self.step_h.clear(); + self.step_x.clear(); + for ym in self.step_y.values_mut() { + ym.clear(); + } + self.step_global_error.clear(); + self.dense_step_index.clear(); + self.dense_x.clear(); + for ym in self.dense_y.values_mut() { + ym.clear(); + } + } + + /// Appends the results after an accepted step is computed + pub(crate) fn push<'a, A>( + &mut self, + work: &Workspace, + x: f64, + y: &Vector, + h: f64, + solver: &Box + 'a>, + ) -> Result<(), StrError> { + // step output + if self.save_step { + self.step_h.push(h); + self.step_x.push(x); + for (m, ym) in self.step_y.iter_mut() { + ym.push(y[*m]); + } + // global error + if let Some(ana) = self.y_analytical.as_mut() { + let ndim = y.dim(); + if self.y_aux.dim() != ndim { + self.y_aux = Vector::new(ndim); // first allocation + } + ana(&mut self.y_aux, x); + let (_, err) = vec_max_abs_diff(y, &self.y_aux).unwrap(); + self.step_global_error.push(err); + } + } + // dense output + if let Some(h_out) = self.dense_h { + if work.bench.n_accepted == 0 { + // first output + self.dense_step_index.push(work.bench.n_accepted); + self.dense_x.push(x); + for (m, ym) in self.dense_y.iter_mut() { + ym.push(y[*m]); + } + } else { + // subsequent output + let ndim = y.dim(); + if self.y_aux.dim() != ndim { + self.y_aux = Vector::new(ndim); // first allocation + } + let mut x_out = self.dense_x.last().unwrap() + h_out; + while x_out < x { + self.dense_step_index.push(work.bench.n_accepted); + self.dense_x.push(x_out); + solver.dense_output(&mut self.y_aux, x_out, x, y, h); + for (m, ym) in self.dense_y.iter_mut() { + ym.push(self.y_aux[*m]); + } + x_out += h_out; + } + } + } + // stiff stations + if self.save_stiff { + if work.stiff_detected { + self.stiff_step_index.push(work.bench.n_accepted); + self.stiff_x.push(x - h); // the detection is always one stepsize earlier + } + } + Ok(()) + } +} diff --git a/russell_ode/src/params.rs b/russell_ode/src/params.rs new file mode 100644 index 00000000..5fafc4f2 --- /dev/null +++ b/russell_ode/src/params.rs @@ -0,0 +1,671 @@ +use crate::{Method, StrError}; +use russell_sparse::{Genie, LinSolParams}; + +/// Holds the local error tolerances and the tolerance for the Newton's method +#[derive(Clone, Copy, Debug)] +pub(crate) struct ParamsTol { + /// Absolute tolerance + pub(crate) abs: f64, + + /// Relative tolerance + pub(crate) rel: f64, + + /// Tolerance for the Newton-Raphson method + pub(crate) newton: f64, +} + +/// Holds parameters for the Newton-Raphson method +#[derive(Clone, Copy, Debug)] +pub struct ParamsNewton { + /// Max number of iterations + /// + /// ```text + /// n_iteration_max ≥ 1 + /// ``` + pub n_iteration_max: usize, + + /// Use numerical Jacobian, even if the analytical Jacobian is available + pub use_numerical_jacobian: bool, + + /// Linear solver kind + pub genie: Genie, + + /// Configurations for sparse linear solver + pub lin_sol_params: Option, +} + +/// Holds parameters to control the variable stepsize algorithm +#[derive(Clone, Copy, Debug)] +pub struct ParamsStep { + /// Min step multiplier + /// + /// ```text + /// 0.001 ≤ m_min < 0.5 and m_min < m_max + /// ``` + pub m_min: f64, + + /// Max step multiplier + /// + /// ```text + /// 0.01 ≤ m_max ≤ 20 and m_max > m_min + /// ``` + pub m_max: f64, + + /// Step multiplier safety factor + /// + /// ```text + /// 0.1 ≤ m ≤ 1 + /// ``` + pub m_safety: f64, + + /// Coefficient to multiply the stepsize if the first step is rejected + /// + /// ```text + /// m_first_reject ≥ 0.0 + /// ``` + /// + /// If `m_first_reject = 0`, the solver will use `h_new` on a rejected step. + pub m_first_reject: f64, + + /// Initial stepsize + /// + /// ```text + /// h_ini ≥ 1e-8 + /// ``` + pub h_ini: f64, + + /// Max number of steps + /// + /// ```text + /// n_step_max ≥ 1 + /// ``` + pub n_step_max: usize, + + /// Min value of previous relative error + /// + /// ```text + /// rel_error_prev_min ≥ 1e-8 + /// ``` + pub rel_error_prev_min: f64, +} + +/// Holds parameters for the stiffness detection algorithm +#[derive(Clone, Copy, Debug)] +pub struct ParamsStiffness { + /// Enables stiffness detection (for some methods such as DoPri5 and DoPri8) + pub enabled: bool, + + /// Return an error if stiffness is detected + /// + /// **Note:** The default is `true`, i.e., the program will stop if stiffness is detected + pub stop_with_error: bool, + + /// Save the results at the stations where stiffness has been detected + /// + /// **Note:** [crate::Output] must be provided to save the results. + pub save_results: bool, + + /// Number of steps to ratify the stiffness, i.e., to make sure that stiffness is repeatedly been detected + pub ratified_after_nstep: usize, + + /// Number of steps to ignore already detected stiffness stations + pub ignored_after_nstep: usize, + + /// Number of initial accepted steps to skip before enabling the stiffness detection + pub skip_first_n_accepted_step: usize, + + /// Holds the max h times lambda coefficient indicating stiffness + pub(crate) h_times_lambda_max: f64, +} + +/// Holds the parameters for the BwEuler method +#[derive(Clone, Copy, Debug)] +pub struct ParamsBwEuler { + /// Use modified Newton's method (constant Jacobian) + pub use_modified_newton: bool, +} + +/// Holds the parameters for the Radau5 method +#[derive(Clone, Copy, Debug)] +pub struct ParamsRadau5 { + /// Always start iterations with zero trial values (instead of collocation interpolation) + pub zero_trial: bool, + + /// Max θ value to decide whether the Jacobian should be recomputed or not + /// + /// ```text + /// theta_max ≥ 1e-7 + /// ``` + pub theta_max: f64, + + /// c1 of Hairer-Wanner (VII p124): min ratio to retain previous h (and thus the Jacobian) + /// + /// ```text + /// 0.5 ≤ c1h ≤ 1.5 and c1h < c2h + /// ```` + pub c1h: f64, + + /// c2 of Hairer-Wanner (VII p124): max ratio to retain previous h (and thus the Jacobian) + /// + /// ```text + /// 1 ≤ c2h ≤ 2 and c2h > c1h + /// ``` + pub c2h: f64, + + /// Enable concurrent factorization and solution of the two linear systems + pub concurrent: bool, + + /// Gustafsson's predictive controller + pub use_pred_control: bool, +} + +/// Holds the parameters for explicit Runge-Kutta methods +#[derive(Clone, Copy, Debug)] +pub struct ParamsERK { + /// Lund stabilization coefficient β + /// + /// ```text + /// 0 ≤ lund_beta ≤ 0.1 + /// ``` + pub lund_beta: f64, + + /// Factor to multiply the Lund stabilization coefficient β + /// + /// ```text + /// 0 ≤ lund_m ≤ 1 + /// ``` + pub lund_m: f64, +} + +/// Holds all parameters for the ODE Solver +#[derive(Clone, Copy, Debug)] +pub struct Params { + /// ODE solver method + pub(crate) method: Method, + + /// Holds the local error tolerances and the tolerance for the Newton's method + pub(crate) tol: ParamsTol, + + /// Holds parameters for the Newton-Raphson method + pub newton: ParamsNewton, + + /// Holds parameters to control the variable stepsize algorithm + pub step: ParamsStep, + + /// Holds parameters for the stiffness detection algorithm + pub stiffness: ParamsStiffness, + + /// Parameters for the BwEuler method + pub bweuler: ParamsBwEuler, + + /// Parameters for the Radau5 method + pub radau5: ParamsRadau5, + + /// Parameters for explicit Runge-Kutta methods + pub erk: ParamsERK, + + /// Enable debugging (print log messages) + pub debug: bool, +} + +// --- Implementations ------------------------------------------------------------------------------------ + +impl ParamsTol { + /// Allocates a new instance + pub(crate) fn new(method: Method) -> Self { + let radau5 = method == Method::Radau5; + let (abs, rel, newton) = calc_tolerances(radau5, 1e-4, 1e-4).unwrap(); + ParamsTol { abs, rel, newton } + } +} + +impl ParamsNewton { + /// Allocates a new instance + pub(crate) fn new() -> Self { + ParamsNewton { + n_iteration_max: 7, // line 436 of radau5.f + use_numerical_jacobian: false, + genie: Genie::Umfpack, + lin_sol_params: None, + } + } + + /// Validates the parameters + pub(crate) fn validate(&self) -> Result<(), StrError> { + if self.n_iteration_max < 1 { + return Err("unmatched requirement: n_iteration_max ≥ 1"); + } + Ok(()) + } +} + +impl ParamsStep { + /// Allocates a new instance + pub(crate) fn new(method: Method) -> Self { + let (m_min, m_max, m_safety, rel_error_prev_min) = match method { + Method::Radau5 => (0.125, 5.0, 0.9, 1e-2), // lines (534, 529, 477, 1018) of radau5.f + Method::DoPri5 => (0.2, 10.0, 0.9, 1e-4), // lines (276, 281, 265, 471) of dopri5.f + Method::DoPri8 => (0.333, 6.0, 0.9, 1e-4), // lines (276, 281, 265, 661) of dop853.f + _ => (0.2, 10.0, 0.9, 1e-4), + }; + ParamsStep { + m_min, + m_max, + m_safety, + m_first_reject: 0.1, + h_ini: 1e-6, + n_step_max: 100000, // lines (426, 212, 211) of (radau5.f, dopri5.f, dop853.f) + rel_error_prev_min, + } + } + + /// Validates the parameters + pub(crate) fn validate(&self) -> Result<(), StrError> { + if self.m_min < 0.001 || self.m_min > 0.5 || self.m_min >= self.m_max { + return Err("unmatched requirement: 0.001 ≤ m_min < 0.5 and m_min < m_max"); + } + if self.m_max < 0.01 || self.m_max > 20.0 { + return Err("unmatched requirement: 0.01 ≤ m_max ≤ 20 and m_max > m_min"); + } + if self.m_safety < 0.1 || self.m_safety > 1.0 { + return Err("unmatched requirement: 0.1 ≤ m_safety ≤ 1"); + } + if self.m_first_reject < 0.0 { + return Err("unmatched requirement: m_first_rejection ≥ 0"); + } + if self.h_ini < 1e-8 { + return Err("unmatched requirement: h_ini ≥ 1e-8"); + } + if self.n_step_max < 1 { + return Err("unmatched requirement: n_step_max ≥ 1"); + } + if self.rel_error_prev_min < 1e-8 { + return Err("unmatched requirement: rel_error_prev_min ≥ 1e-8"); + } + Ok(()) + } +} + +impl ParamsStiffness { + /// Allocates a new instance + pub(crate) fn new(method: Method) -> Self { + let h_times_lambda_max = match method { + Method::DoPri5 => 3.25, // line 482 of dopri5.f + Method::DoPri8 => 6.1, // line 674 of dopri8.f + _ => f64::MAX, // undetermined + }; + ParamsStiffness { + enabled: false, + stop_with_error: true, + save_results: false, + ratified_after_nstep: 15, // lines (485, 677) of (dopri5.f, dop853.f) + ignored_after_nstep: 6, // lines (492, 684) of (dopri5.f, dop853.f) + skip_first_n_accepted_step: 10, + h_times_lambda_max, + } + } +} + +impl ParamsBwEuler { + /// Allocates a new instance + pub(crate) fn new() -> Self { + ParamsBwEuler { + use_modified_newton: false, + } + } +} + +impl ParamsRadau5 { + /// Allocates a new instance + pub(crate) fn new() -> Self { + ParamsRadau5 { + zero_trial: false, + theta_max: 1e-3, // line 487 of radau5.f + c1h: 1.0, // line 508 of radau5.f + c2h: 1.2, // line 513 of radau5.f + concurrent: true, + use_pred_control: true, + } + } + + /// Validates the parameters + pub(crate) fn validate(&self) -> Result<(), StrError> { + if self.theta_max < 1e-7 { + return Err("unmatched requirement: theta_max ≥ 1e-7"); + } + if self.c1h < 0.5 || self.c1h > 1.5 || self.c1h >= self.c2h { + return Err("unmatched requirement: 0.5 ≤ c1h ≤ 1.5 and c1h < c2h"); + } + if self.c2h < 1.0 || self.c2h > 2.0 { + return Err("unmatched requirement: 1 ≤ c2h ≤ 2 and c2h > c1h"); + } + Ok(()) + } +} + +impl ParamsERK { + /// Allocates a new instance + pub(crate) fn new(method: Method) -> Self { + let (lund_beta, lund_m) = match method { + Method::DoPri5 => (0.04, 0.75), // lines (287, 381) of dopri5.f + Method::DoPri8 => (0.0, 0.2), // lines (287, 548) of dop853.f + _ => (0.0, 0.0), + }; + ParamsERK { lund_beta, lund_m } + } + + /// Validates the parameters + pub(crate) fn validate(&self) -> Result<(), StrError> { + if self.lund_beta < 0.0 || self.lund_beta > 0.1 { + return Err("unmatched requirement: 0 ≤ lund_beta ≤ 0.1"); + } + if self.lund_m < 0.0 || self.lund_m > 1.0 { + return Err("unmatched requirement: 0 ≤ lund_m ≤ 1"); + } + Ok(()) + } +} + +impl Params { + /// Allocates a new instance + pub fn new(method: Method) -> Self { + Params { + method, + tol: ParamsTol::new(method), + newton: ParamsNewton::new(), + step: ParamsStep::new(method), + stiffness: ParamsStiffness::new(method), + bweuler: ParamsBwEuler::new(), + radau5: ParamsRadau5::new(), + erk: ParamsERK::new(method), + debug: false, + } + } + + /// Sets the tolerances + pub fn set_tolerances(&mut self, absolute: f64, relative: f64, newton: Option) -> Result<(), StrError> { + let radau5 = self.method == Method::Radau5; + let (abs, rel, newt) = calc_tolerances(radau5, absolute, relative)?; + self.tol.abs = abs; + self.tol.rel = rel; + self.tol.newton = if let Some(n) = newton { n } else { newt }; + Ok(()) + } + + /// Validates all parameters + pub(crate) fn validate(&self) -> Result<(), StrError> { + self.newton.validate()?; + self.step.validate()?; + self.radau5.validate()?; + self.erk.validate()?; + Ok(()) + } +} + +/// Calculates tolerances +/// +/// # Input +/// +/// * `radau5` -- indicates that the tolerances must be altered for Radau5 +/// * `abs_tol` -- absolute tolerance +/// * `rel_tol` -- relative tolerance +/// +/// # Output +/// +/// Returns `(abs_tol, rel_tol, tol_newton)` where: +/// +/// * `abs_tol` -- absolute tolerance +/// * `rel_tol` -- relative tolerance +/// * `tol_newton` -- tolerance for Newton's method +fn calc_tolerances(radau5: bool, abs_tol: f64, rel_tol: f64) -> Result<(f64, f64, f64), StrError> { + // check + if abs_tol <= 10.0 * f64::EPSILON { + return Err("the absolute tolerance must be > 10 · EPSILON"); + } + if rel_tol <= 10.0 * f64::EPSILON { + return Err("the relative tolerance must be > 10 · EPSILON"); + } + + // set + let mut abs_tol = abs_tol; + let mut rel_tol = rel_tol; + + // change the tolerances (according to radau5.f) + if radau5 { + const BETA: f64 = 2.0 / 3.0; // line 402 of radau5.f + let quot = abs_tol / rel_tol; // line 408 of radau5.f + rel_tol = 0.1 * f64::powf(rel_tol, BETA); // line 409 of radau5.f + abs_tol = rel_tol * quot; // line 410 of radau5.f + } + + // tolerance for iterations (line 500 of radau5.f) + let tol_newton = f64::max(10.0 * f64::EPSILON / rel_tol, f64::min(0.03, f64::sqrt(rel_tol))); + Ok((abs_tol, rel_tol, tol_newton)) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::Method; + use russell_lab::approx_eq; + + #[test] + fn derive_methods_work() { + let tol = ParamsTol::new(Method::Radau5); + let newton = ParamsNewton::new(); + let step = ParamsStep::new(Method::Radau5); + let stiffness = ParamsStiffness::new(Method::Radau5); + let bweuler = ParamsBwEuler::new(); + let radau5 = ParamsRadau5::new(); + let erk = ParamsERK::new(Method::DoPri5); + let params = Params::new(Method::Radau5); + let clone_tol = tol.clone(); + let clone_newton = newton.clone(); + let clone_step = step.clone(); + let clone_stiffness = stiffness.clone(); + let clone_bweuler = bweuler.clone(); + let clone_radau5 = radau5.clone(); + let clone_erk = erk.clone(); + let clone_params = params.clone(); + assert_eq!(format!("{:?}", tol), format!("{:?}", clone_tol)); + assert_eq!(format!("{:?}", newton), format!("{:?}", clone_newton)); + assert_eq!(format!("{:?}", step), format!("{:?}", clone_step)); + assert_eq!(format!("{:?}", stiffness), format!("{:?}", clone_stiffness)); + assert_eq!(format!("{:?}", bweuler), format!("{:?}", clone_bweuler)); + assert_eq!(format!("{:?}", radau5), format!("{:?}", clone_radau5)); + assert_eq!(format!("{:?}", erk), format!("{:?}", clone_erk)); + assert_eq!(format!("{:?}", params), format!("{:?}", clone_params)); + } + + #[test] + fn set_tolerances_captures_errors() { + for method in [Method::Radau5, Method::DoPri5] { + let mut params = Params::new(method); + assert_eq!( + params.set_tolerances(0.0, 1e-4, None).err(), + Some("the absolute tolerance must be > 10 · EPSILON") + ); + assert_eq!( + params.set_tolerances(1e-4, 0.0, None).err(), + Some("the relative tolerance must be > 10 · EPSILON") + ); + } + } + + #[test] + fn set_tolerances_works() { + let mut params = Params::new(Method::Radau5); + params.set_tolerances(0.1, 0.1, None).unwrap(); + approx_eq(params.tol.abs, 2.154434690031884E-02, 1e-17); + approx_eq(params.tol.rel, 2.154434690031884E-02, 1e-17); + assert_eq!(params.tol.newton, 0.03); + + params.set_tolerances(0.1, 0.1, Some(0.05)).unwrap(); + approx_eq(params.tol.abs, 2.154434690031884E-02, 1e-17); + approx_eq(params.tol.rel, 2.154434690031884E-02, 1e-17); + assert_eq!(params.tol.newton, 0.05); + + let mut params = Params::new(Method::DoPri5); + params.set_tolerances(0.2, 0.3, None).unwrap(); + assert_eq!(params.tol.abs, 0.2); + assert_eq!(params.tol.rel, 0.3); + } + + #[test] + fn params_newton_validate_works() { + let mut params = ParamsNewton::new(); + params.n_iteration_max = 0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: n_iteration_max ≥ 1") + ); + params.n_iteration_max = 10; + assert_eq!(params.validate().is_err(), false); + } + + #[test] + fn params_step_validate_works() { + let mut params = ParamsStep::new(Method::Radau5); + params.m_min = 0.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.001 ≤ m_min < 0.5 and m_min < m_max") + ); + params.m_min = 0.6; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.001 ≤ m_min < 0.5 and m_min < m_max") + ); + params.m_min = 0.02; + params.m_max = 0.01; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.001 ≤ m_min < 0.5 and m_min < m_max") + ); + params.m_min = 0.001; + params.m_max = 0.005; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.01 ≤ m_max ≤ 20 and m_max > m_min") + ); + params.m_max = 30.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.01 ≤ m_max ≤ 20 and m_max > m_min") + ); + params.m_max = 10.0; + params.m_safety = 0.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.1 ≤ m_safety ≤ 1") + ); + params.m_safety = 1.2; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.1 ≤ m_safety ≤ 1") + ); + params.m_safety = 0.9; + params.m_first_reject = -1.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: m_first_rejection ≥ 0") + ); + params.m_first_reject = 0.0; + params.h_ini = 0.0; + assert_eq!(params.validate().err(), Some("unmatched requirement: h_ini ≥ 1e-8")); + params.h_ini = 1e-4; + params.n_step_max = 0; + assert_eq!(params.validate().err(), Some("unmatched requirement: n_step_max ≥ 1")); + params.n_step_max = 10; + params.rel_error_prev_min = 0.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: rel_error_prev_min ≥ 1e-8") + ); + params.rel_error_prev_min = 1e-6; + assert_eq!(params.validate().is_err(), false); + } + + #[test] + fn params_radau5_validate_works() { + let mut params = ParamsRadau5::new(); + params.theta_max = 0.0; + assert_eq!(params.validate().err(), Some("unmatched requirement: theta_max ≥ 1e-7")); + params.theta_max = 1e-7; + params.c1h = 0.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.5 ≤ c1h ≤ 1.5 and c1h < c2h") + ); + params.c1h = 2.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.5 ≤ c1h ≤ 1.5 and c1h < c2h") + ); + params.c1h = 1.3; + params.c2h = 1.2; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.5 ≤ c1h ≤ 1.5 and c1h < c2h") + ); + params.c1h = 1.0; + params.c2h = 3.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 1 ≤ c2h ≤ 2 and c2h > c1h") + ); + params.c2h = 1.2; + assert_eq!(params.validate().is_err(), false); + } + + #[test] + fn params_erk_validate_works() { + let mut params = ParamsERK::new(Method::DoPri5); + params.lund_beta = -1.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0 ≤ lund_beta ≤ 0.1") + ); + params.lund_beta = 0.2; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0 ≤ lund_beta ≤ 0.1") + ); + params.lund_beta = 0.1; + params.lund_m = -1.0; + assert_eq!(params.validate().err(), Some("unmatched requirement: 0 ≤ lund_m ≤ 1")); + params.lund_m = 1.1; + assert_eq!(params.validate().err(), Some("unmatched requirement: 0 ≤ lund_m ≤ 1")); + params.lund_m = 0.75; + assert_eq!(params.validate().is_err(), false); + } + + #[test] + fn params_validate_works() { + let mut params = Params::new(Method::Radau5); + params.newton.n_iteration_max = 0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: n_iteration_max ≥ 1") + ); + params.newton.n_iteration_max = 10; + params.step.m_min = 0.0; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0.001 ≤ m_min < 0.5 and m_min < m_max") + ); + params.step.m_min = 0.001; + params.radau5.theta_max = 0.0; + assert_eq!(params.validate().err(), Some("unmatched requirement: theta_max ≥ 1e-7")); + params.radau5.theta_max = 1e-7; + params.erk.lund_beta = -0.1; + assert_eq!( + params.validate().err(), + Some("unmatched requirement: 0 ≤ lund_beta ≤ 0.1") + ); + params.erk.lund_beta = 0.1; + assert_eq!(params.validate().is_err(), false); + } +} diff --git a/russell_ode/src/prelude.rs b/russell_ode/src/prelude.rs new file mode 100644 index 00000000..571b0750 --- /dev/null +++ b/russell_ode/src/prelude.rs @@ -0,0 +1,12 @@ +//! Makes available common structures and functions to perform computations +//! +//! You may write `use russell_ode::prelude::*` in your code and obtain +//! access to commonly used functionality. + +pub use crate::benchmark::*; +pub use crate::enums::*; +pub use crate::ode_solver::*; +pub use crate::output::*; +pub use crate::params::*; +pub use crate::samples::*; +pub use crate::system::*; diff --git a/russell_ode/src/radau5.rs b/russell_ode/src/radau5.rs new file mode 100644 index 00000000..71ba11cc --- /dev/null +++ b/russell_ode/src/radau5.rs @@ -0,0 +1,955 @@ +use crate::StrError; +use crate::{OdeSolverTrait, Params, System, Workspace}; +use num_complex::Complex64; +use russell_lab::math::SQRT_6; +use russell_lab::{complex_vec_zip, cpx, format_fortran, vec_copy, ComplexVector, Vector}; +use russell_sparse::{ComplexLinSolver, ComplexSparseMatrix, CooMatrix, Genie, LinSolver, SparseMatrix}; +use std::thread; + +/// Implements the Radau5 method +pub(crate) struct Radau5<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Holds the parameters + params: Params, + + /// ODE system + system: &'a System, + + /// Holds the Jacobian matrix. J = df/dy + jj: SparseMatrix, + + /// Coefficient matrix (for real system). K_real = γ M - J + kk_real: SparseMatrix, + + /// Coefficient matrix (for real system). K_comp = (α + βi) M - J + kk_comp: ComplexSparseMatrix, + + /// Linear solver (for real system) + solver_real: LinSolver<'a>, + + /// Linear solver (for complex system) + solver_comp: ComplexLinSolver<'a>, + + /// Indicates that the Jacobian can be reused (once) + reuse_jacobian: bool, + + /// Indicates that the J, K_real, and K_comp matrices (and their factorizations) can be reused (once) + reuse_jacobian_kk_and_fact: bool, + + /// Indicates that the Jacobian has been computed + /// + /// This flag assists in reusing the Jacobian if the step has been rejected. + /// Make sure to set this flag to false in `accept`. + jacobian_computed: bool, + + /// eta tolerance for stepsize control + eta: f64, + + /// theta variable for stepsize control + theta: f64, + + /// First function evaluation (for each accepted step) + k_accepted: Vector, + + /// Scaling vector + /// + /// ```text + /// scaling[i] = abs_tol + rel_tol ⋅ |y[i]| + /// ``` + scaling: Vector, + + /// Vectors holding the updates. CONT1 of radau5.f + /// + /// ```text + /// v[stg][dim] = ya[dim] + h*sum(a[stg][j]*f[j][dim], j, nstage) + /// ``` + v0: Vector, + v1: Vector, + v2: Vector, + v12: ComplexVector, // packed (v1, v2) + + /// Vectors holding the function evaluations. F{1,2,3} of radau5.f + /// + /// ```text + /// k[stg][dim] = f(u[stg], v[stg][dim]) + /// ``` + k0: Vector, + k1: Vector, + k2: Vector, + + /// Normalized vectors, one for each of the 3 stages. Z{1,2,3} of radau5.f + z0: Vector, + z1: Vector, + z2: Vector, + + /// Collocation values, one for each of the 3 stages. CONT{2,3,4} of radau5.f + yc0: Vector, + yc1: Vector, + yc2: Vector, + + /// Workspace, one for each of the 3 stages + w0: Vector, + w1: Vector, + w2: Vector, + + /// Incremental workspace, one for each of the 3 stages + dw0: Vector, + dw1: Vector, + dw2: Vector, + dw12: ComplexVector, // packed (dw1, dw2) +} + +impl<'a, F, J, A> Radau5<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Allocates a new instance + pub fn new(params: Params, system: &'a System) -> Self { + let ndim = system.ndim; + let symmetry = Some(system.jac_symmetry); + let mass_nnz = match system.mass_matrix.as_ref() { + Some(mass) => mass.get_info().2, + None => ndim, + }; + let jac_nnz = if params.newton.use_numerical_jacobian { + ndim * ndim + } else { + system.jac_nnz + }; + let nnz = mass_nnz + jac_nnz; + let theta = params.radau5.theta_max; + Radau5 { + params, + system, + jj: SparseMatrix::new_coo(ndim, ndim, jac_nnz, symmetry).unwrap(), + kk_real: SparseMatrix::new_coo(ndim, ndim, nnz, symmetry).unwrap(), + kk_comp: ComplexSparseMatrix::new_coo(ndim, ndim, nnz, symmetry).unwrap(), + solver_real: LinSolver::new(params.newton.genie).unwrap(), + solver_comp: ComplexLinSolver::new(params.newton.genie).unwrap(), + reuse_jacobian: false, + reuse_jacobian_kk_and_fact: false, + jacobian_computed: false, + eta: 1.0, + theta, + k_accepted: Vector::new(ndim), + scaling: Vector::new(ndim), + v0: Vector::new(ndim), + v1: Vector::new(ndim), + v2: Vector::new(ndim), + v12: ComplexVector::new(ndim), + k0: Vector::new(ndim), + k1: Vector::new(ndim), + k2: Vector::new(ndim), + z0: Vector::new(ndim), + z1: Vector::new(ndim), + z2: Vector::new(ndim), + yc0: Vector::new(ndim), + yc1: Vector::new(ndim), + yc2: Vector::new(ndim), + w0: Vector::new(ndim), + w1: Vector::new(ndim), + w2: Vector::new(ndim), + dw0: Vector::new(ndim), + dw1: Vector::new(ndim), + dw2: Vector::new(ndim), + dw12: ComplexVector::new(ndim), + } + } + + /// Initializes the scaling and k_accepted vectors + fn initialize(&mut self, work: &mut Workspace, x: f64, y: &Vector, args: &mut A) -> Result<(), StrError> { + for i in 0..self.system.ndim { + self.scaling[i] = self.params.tol.abs + self.params.tol.rel * f64::abs(y[i]); + } + work.bench.n_function += 1; + (self.system.function)(&mut self.k_accepted, x, y, args) + } + + /// Assembles the K_real and K_comp matrices + fn assemble(&mut self, work: &mut Workspace, x: f64, y: &Vector, h: f64, args: &mut A) -> Result<(), StrError> { + // auxiliary + let jj = self.jj.get_coo_mut().unwrap(); // J = df/dy + let kk_real = self.kk_real.get_coo_mut().unwrap(); // K_real = γ M - J + let kk_comp = self.kk_comp.get_coo_mut().unwrap(); // K_comp = (α + βi) M - J + + // Jacobian matrix + if self.reuse_jacobian { + self.reuse_jacobian = false; // just once + } else if !self.jacobian_computed { + work.bench.sw_jacobian.reset(); + work.bench.n_jacobian += 1; + if self.params.newton.use_numerical_jacobian || !self.system.jac_available { + work.bench.n_function += self.system.ndim; + let y_mut = &mut self.w0; // using w[0] as a workspace + let aux = &mut self.dw0; // using dw0 as a workspace + vec_copy(y_mut, y).unwrap(); + self.system + .numerical_jacobian(jj, x, y_mut, &self.k_accepted, 1.0, args, aux)?; + } else { + (self.system.jacobian)(jj, x, y, 1.0, args)?; + } + self.jacobian_computed = true; + work.bench.stop_sw_jacobian(); + } + + // coefficient matrices + let alpha = ALPHA / h; + let beta = BETA / h; + let gamma = GAMMA / h; + kk_real.assign(-1.0, jj).unwrap(); // K_real = -J + kk_comp.assign_real(-1.0, 0.0, jj).unwrap(); // K_comp = -J + match self.system.mass_matrix.as_ref() { + Some(mass) => { + kk_real.augment(gamma, mass).unwrap(); // K_real += γ M + kk_comp.augment_real(alpha, beta, mass).unwrap(); // K_comp += (α + βi) M + } + None => { + for m in 0..self.system.ndim { + kk_real.put(m, m, gamma).unwrap(); // K_real += γ I + kk_comp.put(m, m, cpx!(alpha, beta)).unwrap(); // K_comp += (α + βi) I + } + } + } + Ok(()) + } + + /// Factorizes the real and complex systems in serial + fn factorize(&mut self) -> Result<(), StrError> { + self.solver_real + .actual + .factorize(&mut self.kk_real, self.params.newton.lin_sol_params)?; + self.solver_comp + .actual + .factorize(&mut self.kk_comp, self.params.newton.lin_sol_params) + } + + /// Factorizes the real and complex systems concurrently + fn factorize_concurrently(&mut self) -> Result<(), StrError> { + thread::scope(|scope| { + let handle_real = scope.spawn(|| { + self.solver_real + .actual + .factorize(&mut self.kk_real, self.params.newton.lin_sol_params) + .unwrap(); + }); + let handle_comp = scope.spawn(|| { + self.solver_comp + .actual + .factorize(&mut self.kk_comp, self.params.newton.lin_sol_params) + .unwrap(); + }); + let err_real = handle_real.join(); + let err_comp = handle_comp.join(); + if err_real.is_err() && err_comp.is_err() { + Err("real and complex factorizations failed") + } else if err_real.is_err() { + Err("real factorizations failed") + } else if err_comp.is_err() { + Err("complex factorizations failed") + } else { + Ok(()) + } + }) + } + + /// Solves the real and complex linear systems + fn solve_lin_sys(&mut self) -> Result<(), StrError> { + self.solver_real + .actual + .solve(&mut self.dw0, &self.kk_real, &self.v0, false)?; + self.solver_comp + .actual + .solve(&mut self.dw12, &self.kk_comp, &self.v12, false)?; + Ok(()) + } + + /// Solves the real and complex linear systems concurrently + fn solve_lin_sys_concurrently(&mut self) -> Result<(), StrError> { + thread::scope(|scope| { + let handle_real = scope.spawn(|| { + self.solver_real + .actual + .solve(&mut self.dw0, &self.kk_real, &self.v0, false) + .unwrap(); + }); + let handle_comp = scope.spawn(|| { + self.solver_comp + .actual + .solve(&mut self.dw12, &self.kk_comp, &self.v12, false) + .unwrap(); + }); + let err_real = handle_real.join(); + let err_comp = handle_comp.join(); + if err_real.is_err() && err_comp.is_err() { + Err("real and complex solutions failed") + } else if err_real.is_err() { + Err("real solution failed") + } else if err_comp.is_err() { + Err("complex solution failed") + } else { + Ok(()) + } + }) + } +} + +impl<'a, F, J, A> OdeSolverTrait for Radau5<'a, F, J, A> +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Enables dense output + fn enable_dense_output(&mut self) -> Result<(), StrError> { + Ok(()) + } + + /// Calculates the quantities required to update x and y + fn step(&mut self, work: &mut Workspace, x: f64, y: &Vector, h: f64, args: &mut A) -> Result<(), StrError> { + // Perform the initialization for the first time + if work.bench.n_accepted == 0 { + self.initialize(work, x, y, args)?; + } + + // constants + let concurrent = self.params.radau5.concurrent && self.params.newton.genie != Genie::Mumps; + let ndim = self.system.ndim; + + // Jacobian, K_real, K_comp, and factorizations (for all iterations: simple Newton's method) + if self.reuse_jacobian_kk_and_fact { + self.reuse_jacobian_kk_and_fact = false; // just once + } else { + self.assemble(work, x, y, h, args)?; + work.bench.sw_factor.reset(); + work.bench.n_factor += 1; + if concurrent { + self.factorize_concurrently()?; + } else { + self.factorize()?; + } + work.bench.stop_sw_factor(); + } + + // update u + let u0 = x + C[0] * h; + let u1 = x + C[1] * h; + let u2 = x + C[2] * h; + + // starting values for newton iterations (first z and w) + if work.bench.n_accepted == 0 || self.params.radau5.zero_trial { + // zero trial + for m in 0..ndim { + self.z0[m] = 0.0; + self.z1[m] = 0.0; + self.z2[m] = 0.0; + self.w0[m] = 0.0; + self.w1[m] = 0.0; + self.w2[m] = 0.0; + } + } else { + // polynomial trial + let c3q = h / work.h_prev; + let c1q = MU1 * c3q; + let c2q = MU2 * c3q; + for m in 0..ndim { + self.z0[m] = c1q * (self.yc0[m] + (c1q - MU4) * (self.yc1[m] + (c1q - MU3) * self.yc2[m])); + self.z1[m] = c2q * (self.yc0[m] + (c2q - MU4) * (self.yc1[m] + (c2q - MU3) * self.yc2[m])); + self.z2[m] = c3q * (self.yc0[m] + (c3q - MU4) * (self.yc1[m] + (c3q - MU3) * self.yc2[m])); + self.w0[m] = TI[0][0] * self.z0[m] + TI[0][1] * self.z1[m] + TI[0][2] * self.z2[m]; + self.w1[m] = TI[1][0] * self.z0[m] + TI[1][1] * self.z1[m] + TI[1][2] * self.z2[m]; + self.w2[m] = TI[2][0] * self.z0[m] + TI[2][1] * self.z1[m] + TI[2][2] * self.z2[m]; + } + } + + // auxiliary + let dim = ndim as f64; + let alpha = ALPHA / h; + let beta = BETA / h; + let gamma = GAMMA / h; + self.eta = f64::powf(f64::max(self.eta, f64::EPSILON), 0.8); // FACCON on line 914 of radau5.f + self.theta = self.params.radau5.theta_max; + let mut ldw_old = 0.0; + let mut thq_old = 0.0; + + // iterations + let mut success = false; + work.iterations_diverging = false; + work.bench.n_iterations = 0; // line 931 of radau5.f + for _ in 0..self.params.newton.n_iteration_max { + // benchmark + work.bench.n_iterations += 1; + + // evaluate f(x,y) at (u[i], v[i] = y+z[i]) + for m in 0..ndim { + self.v0[m] = y[m] + self.z0[m]; + self.v1[m] = y[m] + self.z1[m]; + self.v2[m] = y[m] + self.z2[m]; + } + work.bench.n_function += 3; + (self.system.function)(&mut self.k0, u0, &self.v0, args)?; + (self.system.function)(&mut self.k1, u1, &self.v1, args)?; + (self.system.function)(&mut self.k2, u2, &self.v2, args)?; + + // compute the right-hand side vectors + let (l0, l1, l2) = match self.system.mass_matrix.as_ref() { + Some(mass) => { + mass.mat_vec_mul(&mut self.dw0, 1.0, &self.w0).unwrap(); // dw0 := M ⋅ w0 + mass.mat_vec_mul(&mut self.dw1, 1.0, &self.w1).unwrap(); // dw1 := M ⋅ w1 + mass.mat_vec_mul(&mut self.dw2, 1.0, &self.w2).unwrap(); // dw2 := M ⋅ w2 + (&self.dw0, &self.dw1, &self.dw2) + } + None => (&self.w0, &self.w1, &self.w2), + }; + { + // TODO: use rustfmt::skip + let (k0, k1, k2) = (&self.k0, &self.k1, &self.k2); + for m in 0..ndim { + self.v0[m] = TI[0][0] * k0[m] + TI[0][1] * k1[m] + TI[0][2] * k2[m] - gamma * l0[m]; + self.v1[m] = TI[1][0] * k0[m] + TI[1][1] * k1[m] + TI[1][2] * k2[m] - alpha * l1[m] + beta * l2[m]; + self.v2[m] = TI[2][0] * k0[m] + TI[2][1] * k1[m] + TI[2][2] * k2[m] - beta * l1[m] - alpha * l2[m]; + } + } + + // zip vectors + complex_vec_zip(&mut self.v12, &self.v1, &self.v2).unwrap(); + + // solve the linear systems + work.bench.sw_lin_sol.reset(); + work.bench.n_lin_sol += 1; + if concurrent { + self.solve_lin_sys_concurrently()?; + } else { + self.solve_lin_sys()?; + } + work.bench.stop_sw_lin_sol(); + + // update w and z + for m in 0..ndim { + self.w0[m] += self.dw0[m]; + self.w1[m] += self.dw12[m].re; + self.w2[m] += self.dw12[m].im; + self.z0[m] = T[0][0] * self.w0[m] + T[0][1] * self.w1[m] + T[0][2] * self.w2[m]; + self.z1[m] = T[1][0] * self.w0[m] + T[1][1] * self.w1[m] + T[1][2] * self.w2[m]; + self.z2[m] = T[2][0] * self.w0[m] + T[2][1] * self.w1[m] + T[2][2] * self.w2[m]; + } + + // rms norm of δw + let mut ldw = 0.0; + for m in 0..ndim { + let ratio0 = self.dw0[m] / self.scaling[m]; + let ratio1 = self.dw12[m].re / self.scaling[m]; + let ratio2 = self.dw12[m].im / self.scaling[m]; + ldw += ratio0 * ratio0 + ratio1 * ratio1 + ratio2 * ratio2; + } + ldw = f64::sqrt(ldw / (3.0 * dim)); + + // auxiliary + let newt = work.bench.n_iterations; + let nit = self.params.newton.n_iteration_max; + + // print debug messages + if self.params.debug { + println!( + "step = {:>5}, newt = {:>5}, ldw ={}, h ={}", + work.bench.n_steps, + newt, + format_fortran(ldw), + format_fortran(h), + ); + } + + // check convergence + if newt > 1 && newt < nit { + let thq = ldw / ldw_old; + if newt == 2 { + self.theta = thq; + } else { + self.theta = f64::sqrt(thq * thq_old); + } + thq_old = thq; + if self.theta < 0.99 { + self.eta = self.theta / (1.0 - self.theta); // FACCON on line 964 of radau5.f + let exp = (nit - 1 - newt) as f64; // line 967 of radau5.f + let rel_err = self.eta * ldw * f64::powf(self.theta, exp) / self.params.tol.newton; + if rel_err >= 1.0 { + // diverging + let q_newt = f64::max(1.0e-4, f64::min(20.0, rel_err)); + let den = (4 + nit - 1 - newt) as f64; + work.h_multiplier_diverging = 0.8 * f64::powf(q_newt, -1.0 / den); + work.iterations_diverging = true; + return Ok(()); // will try again + } + } else { + // diverging badly (unexpected step-rejection) + work.h_multiplier_diverging = 0.5; + work.iterations_diverging = true; + return Ok(()); // will try again + } + } + + // save old norm + ldw_old = ldw; + + // success + if self.eta * ldw < self.params.tol.newton { + success = true; + break; + } + } + + // check + work.bench.update_n_iterations_max(); + if !success { + return Err("Newton-Raphson method did not complete successfully"); + } + + // error estimate ////////////////////////////////////////////////////// + + // auxiliary + let ez = &mut self.w0; // e times z + let mez = &mut self.w1; // γ M ez or γ ez + let rhs = &mut self.w2; // right-hand side vector + let err = &mut self.dw0; // error variable + + // compute ez, mez and rhs + match self.system.mass_matrix.as_ref() { + Some(mass) => { + for m in 0..ndim { + ez[m] = E0 * self.z0[m] + E1 * self.z1[m] + E2 * self.z2[m]; + } + mass.mat_vec_mul(mez, gamma, ez).unwrap(); + for m in 0..ndim { + rhs[m] = mez[m] + self.k_accepted[m]; // rhs = γ M ez + f0 + } + } + None => { + for m in 0..ndim { + ez[m] = E0 * self.z0[m] + E1 * self.z1[m] + E2 * self.z2[m]; + mez[m] = gamma * ez[m]; + rhs[m] = mez[m] + self.k_accepted[m]; // rhs = γ ez + f0 + } + } + } + + // err := K_real⁻¹ rhs = (γ M - J)⁻¹ rhs (HW-VII p123 Eq.(8.20)) + self.solver_real.actual.solve(err, &self.kk_real, rhs, false)?; + work.rel_error = rms_norm(err, &self.scaling); + + // done with the error estimate + if work.rel_error < 1.0 { + return Ok(()); + } + + // handle particular case + if work.bench.n_accepted == 0 || work.follows_reject_step { + let ype = &mut self.dw1; // y plus err + let fpe = &mut self.dw2; // f(x, y + err) + for m in 0..ndim { + ype[m] = y[m] + err[m]; + } + work.bench.n_function += 1; + (self.system.function)(fpe, x, &ype, args)?; + for m in 0..ndim { + rhs[m] = mez[m] + fpe[m]; + } + self.solver_real.actual.solve(err, &self.kk_real, rhs, false)?; + work.rel_error = rms_norm(err, &self.scaling); + } + Ok(()) + } + + /// Updates x and y and computes the next stepsize + fn accept( + &mut self, + work: &mut Workspace, + x: &mut f64, + y: &mut Vector, + h: f64, + args: &mut A, + ) -> Result<(), StrError> { + // do not reuse current Jacobian and decomposition by default + self.reuse_jacobian_kk_and_fact = false; + self.reuse_jacobian = false; + self.jacobian_computed = false; + + // update y and collocation points + for m in 0..self.system.ndim { + y[m] += self.z2[m]; + self.yc0[m] = (self.z1[m] - self.z2[m]) / MU4; + self.yc1[m] = ((self.z0[m] - self.z1[m]) / MU5 - self.yc0[m]) / MU3; + self.yc2[m] = self.yc1[m] - ((self.z0[m] - self.z1[m]) / MU5 - self.z0[m] / MU1) / MU2; + } + + // estimate the new stepsize + let newt = work.bench.n_iterations; + let num = self.params.step.m_safety * ((1 + 2 * self.params.newton.n_iteration_max) as f64); + let den = (newt + 2 * self.params.newton.n_iteration_max) as f64; + let fac = f64::min(self.params.step.m_safety, num / den); + let div = f64::max( + self.params.step.m_min, + f64::min(self.params.step.m_max, f64::powf(work.rel_error, 0.25) / fac), + ); + let mut h_new = h / div; + + // predictive controller of Gustafsson + if self.params.radau5.use_pred_control { + if work.bench.n_accepted > 1 { + let r2 = work.rel_error * work.rel_error; + let rp = work.rel_error_prev; + let fac = (work.h_prev / h) * f64::powf(r2 / rp, 0.25) / self.params.step.m_safety; + let fac = f64::max(self.params.step.m_min, f64::min(self.params.step.m_max, fac)); + let div = f64::max(div, fac); + h_new = h / div; + } + } + + // update h_new if not reusing factorizations + let h_ratio = h_new / h; + self.reuse_jacobian_kk_and_fact = self.theta <= self.params.radau5.theta_max + && h_ratio >= self.params.radau5.c1h + && h_ratio <= self.params.radau5.c2h; + if !self.reuse_jacobian_kk_and_fact { + work.h_new = h_new; + } + + // check θ to decide if at least the Jacobian can be reused + if !self.reuse_jacobian_kk_and_fact { + self.reuse_jacobian = self.theta <= self.params.radau5.theta_max; + } + + // update x + *x += h; + + // re-initialize + self.initialize(work, *x, y, args) + } + + /// Rejects the update + fn reject(&mut self, work: &mut Workspace, h: f64) { + // estimate new stepsize + let newt = work.bench.n_iterations; + let num = self.params.step.m_safety * ((1 + 2 * self.params.newton.n_iteration_max) as f64); + let den = (newt + 2 * self.params.newton.n_iteration_max) as f64; + let fac = f64::min(self.params.step.m_safety, num / den); + let div = f64::max( + self.params.step.m_min, + f64::min(self.params.step.m_max, f64::powf(work.rel_error, 0.25) / fac), + ); + work.h_new = h / div; + } + + /// Computes the dense output with x-h ≤ x_out ≤ x + fn dense_output(&self, y_out: &mut Vector, x_out: f64, x: f64, y: &Vector, h: f64) { + assert!(x_out >= x - h && x_out <= x); + let s = (x_out - x) / h; + for m in 0..self.system.ndim { + y_out[m] = y[m] + s * (self.yc0[m] + (s - MU4) * (self.yc1[m] + (s - MU3) * self.yc2[m])); + } + } +} + +/// Computes the scaled RMS norm +fn rms_norm(err: &Vector, scaling: &Vector) -> f64 { + let ndim = err.dim(); + assert_eq!(scaling.dim(), ndim); + let mut sum = 0.0; + for m in 0..ndim { + let ratio = err[m] / scaling[m]; + sum += ratio * ratio; + } + f64::max(1e-10, f64::sqrt(sum / (ndim as f64))) +} + +// Radau5 constants ------------------------------------------------------------ + +const ALPHA: f64 = 2.6810828736277521338957907432111121010270319565630; +const BETA: f64 = 3.0504301992474105694263776247875679044407041991795; +const GAMMA: f64 = 3.6378342527444957322084185135777757979459360868739; +const E0: f64 = -2.7623054547485993983499285952820549558040707846130; +const E1: f64 = 0.37993559825272887786874736408712686858426119657697; +const E2: f64 = -0.091629609865225789249276201199804926431531138001387; +const MU1: f64 = 0.15505102572168219018027159252941086080340525193433; +const MU2: f64 = 0.64494897427831780981972840747058913919659474806567; +const MU3: f64 = -0.84494897427831780981972840747058913919659474806567; +const MU4: f64 = -0.35505102572168219018027159252941086080340525193433; +const MU5: f64 = -0.48989794855663561963945681494117827839318949613133; + +const C: [f64; 3] = [(4.0 - SQRT_6) / 10.0, (4.0 + SQRT_6) / 10.0, 1.0]; + +const T: [[f64; 3]; 3] = [ + [ + 9.1232394870892942792e-02, + -0.14125529502095420843, + -3.0029194105147424492e-02, + ], + [0.24171793270710701896, 0.20412935229379993199, 0.38294211275726193779], + [0.96604818261509293619, 1.0, 0.0], +]; + +const TI: [[f64; 3]; 3] = [ + [4.3255798900631553510, 0.33919925181580986954, 0.54177053993587487119], + [-4.1787185915519047273, -0.32768282076106238708, 0.47662355450055045196], + [-0.50287263494578687595, 2.5719269498556054292, -0.59603920482822492497], +]; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::Radau5; + use crate::{Method, OdeSolverTrait, Params, Samples, Workspace}; + use russell_lab::{format_fortran, format_scientific, Vector}; + use russell_sparse::Genie; + use serial_test::serial; + + // IMPORTANT: + // Since MUMPS is not thread-safe, we need to use serial_test::serial + + #[test] + fn radau5_works() { + // This test relates to Table 21.13 of Kreyszig's book, page 921 + + // problem + let (system, data, mut args) = Samples::kreyszig_ex4_page920(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let params = Params::new(Method::Radau5); + let mut solver = Radau5::new(params, &system); + let mut work = Workspace::new(Method::Radau5); + + // message + println!("{:>4}{:>23}{:>23}", "step", "err_y0", "err_y1"); + + // numerical approximation + let h = 0.4; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + let mut n_fcn_correct = 0; + for n in 0..2 { + // call step + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + + // update number of function evaluations + let nit = work.bench.n_iterations; + if n == 0 { + assert_eq!(work.bench.n_iterations, 2); + n_fcn_correct += 1 + 3 * nit + 1; // initialize + iterations + error-estimate + } else { + assert_eq!(work.bench.n_iterations, 1); + n_fcn_correct += 3 * nit; // iterations + } + + // important: update n_accepted (must precede `accept`) + work.bench.n_accepted += 1; + + // call accept + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + + // important: save previous stepsize and relative error (must succeed `accept`) + work.h_prev = h; + work.rel_error_prev = f64::max(params.step.rel_error_prev_min, work.rel_error); + + // update number of function evaluations + n_fcn_correct += 1; // re-initialize + + // check the results + yfx(&mut y_ana, x); + let err_y0 = f64::abs(y[0] - y_ana[0]); + let err_y1 = f64::abs(y[1] - y_ana[1]); + println!("{:>4}{}{}", n, format_fortran(err_y0), format_fortran(err_y1)); + if n == 0 { + assert!(err_y0 < 1.1e-2); + assert!(err_y1 < 1.1e-1); + } else { + assert!(err_y0 < 5.15e-4); + assert!(err_y1 < 5.15e-3); + } + } + + // check number of function evaluations + assert_eq!(work.bench.n_function, n_fcn_correct); + assert_eq!(work.bench.n_jacobian, 1); // simple Newton's method + } + + #[test] + fn radau5_works_num_jacobian() { + // This test relates to Table 21.13 of Kreyszig's book, page 921 + + // problem + let (system, data, mut args) = Samples::kreyszig_ex4_page920(); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let mut params = Params::new(Method::Radau5); + params.newton.use_numerical_jacobian = true; + let mut solver = Radau5::new(params, &system); + let mut work = Workspace::new(Method::Radau5); + + // message + println!("{:>4}{:>23}{:>23}", "step", "err_y0", "err_y1"); + + // numerical approximation + let h = 0.4; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + let mut n_fcn_correct = 0; + for n in 0..2 { + // call step + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + + // update number of function evaluations + let nit = work.bench.n_iterations; + if n == 0 { + assert_eq!(work.bench.n_iterations, 2); + n_fcn_correct += 1 + 3 * nit + 1; // initialize + iterations + error-estimate + n_fcn_correct += ndim; // to compute Jacobian (on the first step; simple Newton) + } else { + assert_eq!(work.bench.n_iterations, 2); // 1 iteration more than with analytical Jacobian + n_fcn_correct += 3 * nit; // iterations + } + + // important: update n_accepted (must precede `accept`) + work.bench.n_accepted += 1; + + // call accept + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + + // important: save previous stepsize and relative error (must succeed `accept`) + work.h_prev = h; + work.rel_error_prev = f64::max(params.step.rel_error_prev_min, work.rel_error); + + // update number of function evaluations + n_fcn_correct += 1; // re-initialize + + // check the results + yfx(&mut y_ana, x); + let err_y0 = f64::abs(y[0] - y_ana[0]); + let err_y1 = f64::abs(y[1] - y_ana[1]); + println!("{:>4}{}{}", n, format_fortran(err_y0), format_fortran(err_y1)); + if n == 0 { + assert!(err_y0 < 1.1e-2); + assert!(err_y1 < 1.1e-1); + } else { + assert!(err_y0 < 5.15e-4); + assert!(err_y1 < 5.15e-3); + } + } + + // check number of function evaluations + assert_eq!(work.bench.n_function, n_fcn_correct); + } + + #[test] + fn radau5_works_mass_matrix() { + // problem + let (system, data, mut args) = Samples::simple_system_with_mass_matrix(false); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let params = Params::new(Method::Radau5); + let mut solver = Radau5::new(params, &system); + let mut work = Workspace::new(Method::Radau5); + + // message + println!("{:>4}{:>10}{:>10}{:>10}", "step", "err_y0", "err_y1", "err_y2"); + + // numerical approximation + let h = 0.1; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + for n in 0..5 { + // call step + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + + // important: update n_accepted (must precede `accept`) + work.bench.n_accepted += 1; + + // call accept + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + + // important: save previous stepsize and relative error (must succeed `accept`) + work.h_prev = h; + work.rel_error_prev = f64::max(params.step.rel_error_prev_min, work.rel_error); + + // check the results + yfx(&mut y_ana, x); + let err_y0 = f64::abs(y[0] - y_ana[0]); + let err_y1 = f64::abs(y[1] - y_ana[1]); + let err_y2 = f64::abs(y[2] - y_ana[2]); + println!( + "{:>4}{}{}{}", + n, + format_scientific(err_y0, 10, 2), + format_scientific(err_y1, 10, 2), + format_scientific(err_y2, 10, 2) + ); + assert!(err_y0 < 1e-9); + assert!(err_y1 < 1e-9); + assert!(err_y2 < 1e-8); + } + } + + #[test] + #[serial] + fn radau5_works_mass_matrix_symmetric_mumps() { + // problem + let (system, data, mut args) = Samples::simple_system_with_mass_matrix(true); + let yfx = data.y_analytical.unwrap(); + let ndim = system.ndim; + + // allocate structs + let mut params = Params::new(Method::Radau5); + params.newton.genie = Genie::Mumps; + let mut solver = Radau5::new(params, &system); + let mut work = Workspace::new(Method::Radau5); + + // message + println!("{:>4}{:>10}{:>10}{:>10}", "step", "err_y0", "err_y1", "err_y2"); + + // numerical approximation + let h = 0.1; + let mut x = data.x0; + let mut y = data.y0.clone(); + let mut y_ana = Vector::new(ndim); + for n in 0..5 { + // call step + solver.step(&mut work, x, &y, h, &mut args).unwrap(); + + // important: update n_accepted (must precede `accept`) + work.bench.n_accepted += 1; + + // call accept + solver.accept(&mut work, &mut x, &mut y, h, &mut args).unwrap(); + + // important: save previous stepsize and relative error (must succeed `accept`) + work.h_prev = h; + work.rel_error_prev = f64::max(params.step.rel_error_prev_min, work.rel_error); + + // check the results + yfx(&mut y_ana, x); + let err_y0 = f64::abs(y[0] - y_ana[0]); + let err_y1 = f64::abs(y[1] - y_ana[1]); + let err_y2 = f64::abs(y[2] - y_ana[2]); + println!( + "{:>4}{}{}{}", + n, + format_scientific(err_y0, 10, 2), + format_scientific(err_y1, 10, 2), + format_scientific(err_y2, 10, 2) + ); + assert!(err_y0 < 1e-9); + assert!(err_y1 < 1e-9); + assert!(err_y2 < 1e-8); + } + } +} diff --git a/russell_ode/src/samples.rs b/russell_ode/src/samples.rs new file mode 100644 index 00000000..085cb23f --- /dev/null +++ b/russell_ode/src/samples.rs @@ -0,0 +1,1050 @@ +use crate::StrError; +use crate::{HasJacobian, NoArgs, System}; +use russell_lab::math::PI; +use russell_lab::Vector; +use russell_sparse::{CooMatrix, Symmetry}; + +/// Holds data corresponding to a sample ODE problem +pub struct SampleData { + /// Holds the initial x + pub x0: f64, + + /// Holds the initial y + pub y0: Vector, + + /// Holds the final x + pub x1: f64, + + /// Holds the stepsize for simulations with equal-steps + pub h_equal: Option, + + /// Holds a function to compute the analytical solution y(x) + pub y_analytical: Option, +} + +/// Holds a collection of sample ODE problems +/// +/// # References +/// +/// 1. Hairer E, Nørsett, SP, Wanner G (2008) Solving Ordinary Differential Equations I. +/// Non-stiff Problems. Second Revised Edition. Corrected 3rd printing 2008. Springer Series +/// in Computational Mathematics, 528p +/// 2. Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. +/// Stiff and Differential-Algebraic Problems. Second Revised Edition. +/// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p +/// 3. Kreyszig, E (2011) Advanced engineering mathematics; in collaboration with Kreyszig H, +/// Edward JN 10th ed 2011, Hoboken, New Jersey, Wiley +pub struct Samples {} + +impl Samples { + /// Implements a simple ODE with a single equation and constant derivative + /// + /// ```text + /// dy + /// —— = 1 with y(x=0)=0 thus y(x) = x + /// dx + /// ``` + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values and the analytical solution + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + pub fn simple_equation_constant() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + let ndim = 1; + let jac_nnz = 1; // CooMatrix requires at least one value (thus the 0.0 must be stored) + let system = System::new( + ndim, + |f: &mut Vector, _x: f64, _y: &Vector, _args: &mut NoArgs| { + f[0] = 1.0; + Ok(()) + }, + |jj: &mut CooMatrix, _x: f64, _y: &Vector, multiplier: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 0, 0.0 * multiplier).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0: 0.0, + y0: Vector::from(&[0.0]), + x1: 1.0, + h_equal: Some(0.2), + y_analytical: Some(|y, x| { + y[0] = x; + }), + }; + (system, data, 0) + } + + /// Returns a simple system with a mass matrix + /// + /// The system is: + /// + /// ```text + /// y0' + y1' = -y0 + y1 + /// y0' - y1' = y0 + y1 + /// y2' = 1/(1 + x) + /// + /// y0(0) = 1, y1(0) = 0, y2(0) = 0 + /// ``` + /// + /// Thus: + /// + /// ```text + /// M y' = f(x, y) + /// + /// with: + /// + /// ┌ ┐ ┌ ┐ + /// │ 1 1 0 │ │ -y0 + y1 │ + /// M = │ 1 -1 0 │ f = │ y0 + y1 │ + /// │ 0 0 1 │ │ 1/(1 + x) │ + /// └ ┘ └ ┘ + /// ``` + /// + /// The Jacobian matrix is: + /// + /// ```text + /// ┌ ┐ + /// df │ -1 1 0 │ + /// J = —— = │ 1 1 0 │ + /// dy │ 0 0 0 │ + /// └ ┘ + /// ``` + /// + /// The analytical solution is: + /// + /// ```text + /// y0(x) = cos(x) + /// y1(x) = -sin(x) + /// y2(x) = log(1 + x) + /// ``` + /// + /// # Input + /// + /// * `lower_triangle` -- Considers the symmetry of the Jacobian and Mass matrices, and generates a lower triangular representation + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + pub fn simple_system_with_mass_matrix( + lower_triangle: bool, + ) -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + // selected symmetry option (for both Mass and Jacobian matrices) + let symmetry = if lower_triangle { + Some(Symmetry::new_general_lower()) + } else { + None + }; + + // initial values + let x0 = 0.0; + let y0 = Vector::from(&[1.0, 0.0, 0.0]); + let x1 = 20.0; + + // ODE system + let ndim = 3; + let jac_nnz = if lower_triangle { 3 } else { 4 }; + let mut system = System::new( + ndim, + move |f: &mut Vector, x: f64, y: &Vector, _args: &mut NoArgs| { + f[0] = -y[0] + y[1]; + f[1] = y[0] + y[1]; + f[2] = 1.0 / (1.0 + x); + Ok(()) + }, + move |jj: &mut CooMatrix, _x: f64, _y: &Vector, m: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 0, m * (-1.0)).unwrap(); + if !lower_triangle { + jj.put(0, 1, m * (1.0)).unwrap(); + } + jj.put(1, 0, m * (1.0)).unwrap(); + jj.put(1, 1, m * (1.0)).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + symmetry, + ); + + // mass matrix + let mass_nnz = if lower_triangle { 4 } else { 5 }; + system.init_mass_matrix(mass_nnz).unwrap(); + system.mass_put(0, 0, 1.0).unwrap(); + if !lower_triangle { + system.mass_put(0, 1, 1.0).unwrap(); + } + system.mass_put(1, 0, 1.0).unwrap(); + system.mass_put(1, 1, -1.0).unwrap(); + system.mass_put(2, 2, 1.0).unwrap(); + + // control + let data = SampleData { + x0, + y0, + x1, + h_equal: None, + y_analytical: Some(|y, x| { + y[0] = f64::cos(x); + y[1] = -f64::sin(x); + y[2] = f64::ln(1.0 + x); + }), + }; + (system, data, 0) + } + + /// Implements Equation (6) from Kreyszig's book on page 902 + /// + /// ```text + /// dy/dx = x + y + /// y(0) = 0 + /// ``` + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values and the analytical solution + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Reference + /// + /// * Kreyszig, E (2011) Advanced engineering mathematics; in collaboration with Kreyszig H, + /// Edward JN 10th ed 2011, Hoboken, New Jersey, Wiley + pub fn kreyszig_eq6_page902() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + let ndim = 1; + let jac_nnz = 1; + let system = System::new( + ndim, + |f: &mut Vector, x: f64, y: &Vector, _args: &mut NoArgs| { + f[0] = x + y[0]; + Ok(()) + }, + |jj: &mut CooMatrix, _x: f64, _y: &Vector, multiplier: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 0, 1.0 * multiplier).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0: 0.0, + y0: Vector::from(&[0.0]), + x1: 1.0, + h_equal: Some(0.2), + y_analytical: Some(|y, x| { + y[0] = f64::exp(x) - x - 1.0; + }), + }; + (system, data, 0) + } + + /// Implements Example 4 from Kreyszig's book on page 920 + /// + /// With proper initial conditions, this problem becomes "stiff". + /// + /// ```text + /// y'' + 11 y' + 10 y = 10 x + 11 + /// y(0) = 2 + /// y'(0) = -10 + /// ``` + /// + /// Converting into a system: + /// + /// ```text + /// y = y1 and y' = y2 + /// y0' = y1 + /// y1' = -10 y0 - 11 y1 + 10 x + 11 + /// ``` + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values and the analytical solution + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Reference + /// + /// * Kreyszig, E (2011) Advanced engineering mathematics; in collaboration with Kreyszig H, + /// Edward JN 10th ed 2011, Hoboken, New Jersey, Wiley + pub fn kreyszig_ex4_page920() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + let ndim = 2; + let jac_nnz = 3; + let system = System::new( + ndim, + |f: &mut Vector, x: f64, y: &Vector, _args: &mut NoArgs| { + f[0] = y[1]; + f[1] = -10.0 * y[0] - 11.0 * y[1] + 10.0 * x + 11.0; + Ok(()) + }, + |jj: &mut CooMatrix, _x: f64, _y: &Vector, multiplier: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 1, 1.0 * multiplier).unwrap(); + jj.put(1, 0, -10.0 * multiplier).unwrap(); + jj.put(1, 1, -11.0 * multiplier).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0: 0.0, + y0: Vector::from(&[2.0, -10.0]), + x1: 1.0, + h_equal: Some(0.2), + y_analytical: Some(|y, x| { + y[0] = f64::exp(-x) + f64::exp(-10.0 * x) + x; + y[1] = -f64::exp(-x) - 10.0 * f64::exp(-10.0 * x) + 1.0; + }), + }; + (system, data, 0) + } + + /// Returns the Hairer-Wanner problem from the reference, Part II, Eq(1.1), page 2 (with analytical solution) + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values and the analytical solution + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Reference + /// + /// * Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. + /// Stiff and Differential-Algebraic Problems. Second Revised Edition. + /// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p + pub fn hairer_wanner_eq1() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + const L: f64 = -50.0; // lambda + let ndim = 1; + let jac_nnz = 1; + let system = System::new( + ndim, + |f: &mut Vector, x: f64, y: &Vector, _args: &mut NoArgs| { + f[0] = L * (y[0] - f64::cos(x)); + Ok(()) + }, + |jj: &mut CooMatrix, _x: f64, _y: &Vector, multiplier: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 0, multiplier * L).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0: 0.0, + y0: Vector::from(&[0.0]), + x1: 1.5, + h_equal: Some(1.875 / 50.0), + y_analytical: Some(|y, x| { + y[0] = -L * (f64::sin(x) - L * f64::cos(x) + L * f64::exp(L * x)) / (L * L + 1.0); + }), + }; + (system, data, 0) + } + + /// Returns the Robertson's equation, Hairer-Wanner, Part II, Eq(1.4), page 3 + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Reference + /// + /// * Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. + /// Stiff and Differential-Algebraic Problems. Second Revised Edition. + /// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p + pub fn robertson() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + let ndim = 3; + let jac_nnz = 7; + let system = System::new( + ndim, + |f: &mut Vector, _x: f64, y: &Vector, _args: &mut NoArgs| { + f[0] = -0.04 * y[0] + 1.0e4 * y[1] * y[2]; + f[1] = 0.04 * y[0] - 1.0e4 * y[1] * y[2] - 3.0e7 * y[1] * y[1]; + f[2] = 3.0e7 * y[1] * y[1]; + Ok(()) + }, + |jj: &mut CooMatrix, _x: f64, y: &Vector, multiplier: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 0, -0.04 * multiplier).unwrap(); + jj.put(0, 1, 1.0e4 * y[2] * multiplier).unwrap(); + jj.put(0, 2, 1.0e4 * y[1] * multiplier).unwrap(); + jj.put(1, 0, 0.04 * multiplier).unwrap(); + jj.put(1, 1, (-1.0e4 * y[2] - 6.0e7 * y[1]) * multiplier).unwrap(); + jj.put(1, 2, (-1.0e4 * y[1]) * multiplier).unwrap(); + jj.put(2, 1, 6.0e7 * y[1] * multiplier).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0: 0.0, + y0: Vector::from(&[1.0, 0.0, 0.0]), + x1: 0.3, + h_equal: None, + y_analytical: None, + }; + (system, data, 0) + } + + /// Returns the Van der Pol's equation as given in Hairer-Wanner, Part II, Eq(1.5'), page 5 + /// + /// **Note:** Using the data from Eq(7.29), page 113. + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Input + /// + /// * `epsilon` -- ε coefficient; use None for the default value (= 1.0e-6) + /// * `stationary` -- use `ε = 1` and compute the period and amplitude such that + /// `y = [A, 0]` is a stationary point. + /// + /// # Reference + /// + /// * Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. + /// Stiff and Differential-Algebraic Problems. Second Revised Edition. + /// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p + pub fn van_der_pol( + epsilon: Option, + stationary: bool, + ) -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + let mut eps = match epsilon { + Some(e) => e, + None => 1.0e-6, + }; + let x0 = 0.0; + let mut y0 = Vector::from(&[2.0, -0.6]); + let mut x1 = 2.0; + if stationary { + eps = 1.0; + const A: f64 = 2.00861986087484313650940188; + const T: f64 = 6.6632868593231301896996820305; + y0[0] = A; + y0[1] = 0.0; + x1 = T; + } + let ndim = 2; + let jac_nnz = 3; + let system = System::new( + ndim, + move |f: &mut Vector, _x: f64, y: &Vector, _args: &mut NoArgs| { + f[0] = y[1]; + f[1] = ((1.0 - y[0] * y[0]) * y[1] - y[0]) / eps; + Ok(()) + }, + move |jj: &mut CooMatrix, _x: f64, y: &Vector, multiplier: f64, _args: &mut NoArgs| { + jj.reset(); + jj.put(0, 1, 1.0 * multiplier).unwrap(); + jj.put(1, 0, multiplier * (-2.0 * y[0] * y[1] - 1.0) / eps).unwrap(); + jj.put(1, 1, multiplier * (1.0 - y[0] * y[0]) / eps).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0, + y0, + x1, + h_equal: None, + y_analytical: None, + }; + (system, data, 0) + } + + /// Returns the Arenstorf orbit problem, Hairer-Wanner, Part I, Eq(0.1), page 129 + /// + /// From Hairer-Wanner: + /// + /// "(...) an example from Astronomy, the restricted three body problem. (...) + /// two bodies of masses μ' = 1 − μ and μ in circular rotation in a plane and + /// a third body of negligible mass moving around in the same plane. (...)" + /// + /// ```text + /// y0'' = y0 + 2 y1' - μ' (y0 + μ) / d0 - μ (y0 - μ') / d1 + /// y1'' = y1 - 2 y0' - μ' y1 / d0 - μ y1 / d1 + /// ``` + /// + /// ```text + /// y2 := y0' ⇒ y2' = y0'' + /// y3 := y1' ⇒ y3' = y1'' + /// ``` + /// + /// ```text + /// f0 := y0' = y2 + /// f1 := y1' = y3 + /// f2 := y2' = y0 + 2 y3 - μ' (y0 + μ) / d0 - μ (y0 - μ') / d1 + /// f3 := y3' = y1 - 2 y2 - μ' y1 / d0 - μ y1 / d1 + /// ``` + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Reference + /// + /// * Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. + /// Stiff and Differential-Algebraic Problems. Second Revised Edition. + /// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p + pub fn arenstorf() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + const MU: f64 = 0.012277471; + const MD: f64 = 1.0 - MU; + let x0 = 0.0; + let y0 = Vector::from(&[0.994, 0.0, 0.0, -2.00158510637908252240537862224]); + let x1 = 17.0652165601579625588917206249; + let ndim = 4; + let jac_nnz = 8; + let system = System::new( + ndim, + |f: &mut Vector, _x: f64, y: &Vector, _args: &mut NoArgs| { + let t0 = (y[0] + MU) * (y[0] + MU) + y[1] * y[1]; + let t1 = (y[0] - MD) * (y[0] - MD) + y[1] * y[1]; + let d0 = t0 * f64::sqrt(t0); + let d1 = t1 * f64::sqrt(t1); + f[0] = y[2]; + f[1] = y[3]; + f[2] = y[0] + 2.0 * y[3] - MD * (y[0] + MU) / d0 - MU * (y[0] - MD) / d1; + f[3] = y[1] - 2.0 * y[2] - MD * y[1] / d0 - MU * y[1] / d1; + Ok(()) + }, + |jj: &mut CooMatrix, _x: f64, y: &Vector, m: f64, _args: &mut NoArgs| { + let t0 = (y[0] + MU) * (y[0] + MU) + y[1] * y[1]; + let t1 = (y[0] - MD) * (y[0] - MD) + y[1] * y[1]; + let s0 = f64::sqrt(t0); + let s1 = f64::sqrt(t1); + let d0 = t0 * s0; + let d1 = t1 * s1; + let dd0 = d0 * d0; + let dd1 = d1 * d1; + let a = y[0] + MU; + let b = y[0] - MD; + let c = -MD / d0 - MU / d1; + let dj00 = 3.0 * a * s0; + let dj01 = 3.0 * y[1] * s0; + let dj10 = 3.0 * b * s1; + let dj11 = 3.0 * y[1] * s1; + jj.reset(); + jj.put(0, 2, 1.0 * m).unwrap(); + jj.put(1, 3, 1.0 * m).unwrap(); + jj.put(2, 0, (1.0 + a * dj00 * MD / dd0 + b * dj10 * MU / dd1 + c) * m) + .unwrap(); + jj.put(2, 1, (a * dj01 * MD / dd0 + b * dj11 * MU / dd1) * m).unwrap(); + jj.put(2, 3, 2.0 * m).unwrap(); + jj.put(3, 0, (dj00 * y[1] * MD / dd0 + dj10 * y[1] * MU / dd1) * m) + .unwrap(); + jj.put(3, 1, (1.0 + dj01 * y[1] * MD / dd0 + dj11 * y[1] * MU / dd1 + c) * m) + .unwrap(); + jj.put(3, 2, -2.0 * m).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + let data = SampleData { + x0, + y0, + x1, + h_equal: None, + y_analytical: None, + }; + (system, data, 0) + } + + /// Returns the one-transistor amplifier problem described by Hairer-Wanner, Part II, page 376 + /// + /// # Output + /// + /// Returns `(system, data, args)` where: + /// + /// * `system: System` with: + /// * `F` -- is a function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` + /// * `J` -- is a function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` + /// * `A` -- is `NoArgs` + /// * `data: SampleData` -- holds the initial values + /// * `args: NoArgs` -- is a placeholder variable with the arguments to F and J + /// + /// # Reference + /// + /// * Hairer E, Wanner G (2002) Solving Ordinary Differential Equations II. + /// Stiff and Differential-Algebraic Problems. Second Revised Edition. + /// Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p + pub fn amplifier1t() -> ( + System< + impl Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + impl Fn(&mut CooMatrix, f64, &Vector, f64, &mut NoArgs) -> Result<(), StrError>, + NoArgs, + >, + SampleData, + NoArgs, + ) { + // constants + const ALPHA: f64 = 0.99; + const GAMMA: f64 = 1.0 - ALPHA; + const C: f64 = 0.4; + const D: f64 = 200.0 * PI; + const BETA: f64 = 1e-6; + const UB: f64 = 6.0; + const UF: f64 = 0.026; + const R: f64 = 1000.0; + const S: f64 = 9000.0; + + // initial values + let x0 = 0.0; + let y0 = Vector::from(&[0.0, UB / 2.0, UB / 2.0, UB, 0.0]); + let x1 = 0.05; + + // ODE system + let ndim = 5; + let jac_nnz = 9; + let mut system = System::new( + ndim, + move |f: &mut Vector, x: f64, y: &Vector, _args: &mut NoArgs| { + let ue = C * f64::sin(D * x); + let f12 = BETA * (f64::exp((y[1] - y[2]) / UF) - 1.0); + f[0] = (y[0] - ue) / R; + f[1] = (2.0 * y[1] - UB) / S + GAMMA * f12; + f[2] = y[2] / S - f12; + f[3] = (y[3] - UB) / S + ALPHA * f12; + f[4] = y[4] / S; + Ok(()) + }, + move |jj: &mut CooMatrix, _x: f64, y: &Vector, m: f64, _args: &mut NoArgs| { + let g12 = BETA * f64::exp((y[1] - y[2]) / UF) / UF; + jj.reset(); + jj.put(0, 0, m * (1.0 / R)).unwrap(); + jj.put(1, 1, m * (2.0 / S + GAMMA * g12)).unwrap(); + jj.put(1, 2, m * (-GAMMA * g12)).unwrap(); + jj.put(2, 1, m * (-g12)).unwrap(); + jj.put(2, 2, m * (1.0 / S + g12)).unwrap(); + jj.put(3, 1, m * (ALPHA * g12)).unwrap(); + jj.put(3, 2, m * (-ALPHA * g12)).unwrap(); + jj.put(3, 3, m * (1.0 / S)).unwrap(); + jj.put(4, 4, m * (1.0 / S)).unwrap(); + Ok(()) + }, + HasJacobian::Yes, + Some(jac_nnz), + None, + ); + + // function that generates the mass matrix + const C1: f64 = 1e-6; + const C2: f64 = 2e-6; + const C3: f64 = 3e-6; + let mass_nnz = 9; + system.init_mass_matrix(mass_nnz).unwrap(); + system.mass_put(0, 0, -C1).unwrap(); + system.mass_put(0, 1, C1).unwrap(); + system.mass_put(1, 0, C1).unwrap(); + system.mass_put(1, 1, -C1).unwrap(); + system.mass_put(2, 2, -C2).unwrap(); + system.mass_put(3, 3, -C3).unwrap(); + system.mass_put(3, 4, C3).unwrap(); + system.mass_put(4, 3, C3).unwrap(); + system.mass_put(4, 4, -C3).unwrap(); + + // control + let data = SampleData { + x0, + y0, + x1, + h_equal: None, + y_analytical: None, + }; + (system, data, 0) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::{NoArgs, Samples}; + use crate::StrError; + use russell_lab::{deriv_central5, mat_approx_eq, vec_approx_eq, Matrix, Vector}; + use russell_sparse::{CooMatrix, Symmetry}; + + fn numerical_jacobian(ndim: usize, x0: f64, y0: Vector, function: F, multiplier: f64) -> Matrix + where + F: Fn(&mut Vector, f64, &Vector, &mut NoArgs) -> Result<(), StrError>, + { + struct Extra { + x: f64, + f: Vector, + y: Vector, + i: usize, // index i of ∂fᵢ/∂yⱼ + j: usize, // index j of ∂fᵢ/∂yⱼ + } + let mut extra = Extra { + x: x0, + f: Vector::new(ndim), + y: y0.clone(), + i: 0, + j: 0, + }; + let mut jac = Matrix::new(ndim, ndim); + let mut args: u8 = 0; + for i in 0..ndim { + extra.i = i; + for j in 0..ndim { + extra.j = j; + let at_yj = y0[j]; + let res = deriv_central5(at_yj, &mut extra, |yj: f64, extra: &mut Extra| { + let original = extra.y[extra.j]; + extra.y[extra.j] = yj; + function(&mut extra.f, extra.x, &extra.y, &mut args).unwrap(); + extra.y[extra.j] = original; + extra.f[extra.i] + }); + jac.set(i, j, res * multiplier); + } + } + jac + } + + #[test] + fn simple_constant_works() { + let multiplier = 2.0; + let (system, mut data, mut args) = Samples::simple_equation_constant(); + + // check initial values + if let Some(y_ana) = data.y_analytical.as_mut() { + let mut y = Vector::new(data.y0.dim()); + y_ana(&mut y, data.x0); + println!("y0 = {:?} = {:?}", y.as_data(), data.y0.as_data()); + vec_approx_eq(y.as_data(), data.y0.as_data(), 1e-15); + } + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1e-11); + } + + #[test] + fn kreyszig_eq6_page902_works() { + let multiplier = 2.0; + let (system, mut data, mut args) = Samples::kreyszig_eq6_page902(); + + // check initial values + if let Some(y_ana) = data.y_analytical.as_mut() { + let mut y = Vector::new(data.y0.dim()); + y_ana(&mut y, data.x0); + println!("y0 = {:?} = {:?}", y.as_data(), data.y0.as_data()); + vec_approx_eq(y.as_data(), data.y0.as_data(), 1e-15); + } + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1e-11); + } + + #[test] + fn kreyszig_ex4_page920() { + let multiplier = 2.0; + let (system, mut data, mut args) = Samples::kreyszig_ex4_page920(); + + // check initial values + if let Some(y_ana) = data.y_analytical.as_mut() { + let mut y = Vector::new(data.y0.dim()); + y_ana(&mut y, data.x0); + println!("y0 = {:?} = {:?}", y.as_data(), data.y0.as_data()); + vec_approx_eq(y.as_data(), data.y0.as_data(), 1e-15); + } + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1e-10); + } + + #[test] + fn hairer_wanner_eq1_works() { + let multiplier = 2.0; + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + + // check initial values + if let Some(y_ana) = data.y_analytical.as_mut() { + let mut y = Vector::new(data.y0.dim()); + y_ana(&mut y, data.x0); + println!("y0 = {:?} = {:?}", y.as_data(), data.y0.as_data()); + vec_approx_eq(y.as_data(), data.y0.as_data(), 1e-15); + } + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1e-11); + } + + #[test] + fn robertson_works() { + let multiplier = 2.0; + let (system, data, mut args) = Samples::robertson(); + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1e-14); + } + + #[test] + fn van_der_pol_works() { + let multiplier = 2.0; + let (system, data, mut args) = Samples::van_der_pol(None, false); + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1.5e-6); + } + + #[test] + fn van_der_pol_works_stationary() { + let multiplier = 3.0; + let (system, data, mut args) = Samples::van_der_pol(None, true); + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1e-11); + } + + #[test] + fn arenstorf_works() { + let multiplier = 1.5; + let (system, data, mut args) = Samples::arenstorf(); + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{}", ana); + println!("{}", num); + mat_approx_eq(&ana, &num, 1.6e-4); + } + + #[test] + fn amplifier1t_works() { + let multiplier = 2.0; + let (system, data, mut args) = Samples::amplifier1t(); + + // compute the analytical Jacobian matrix + let symmetry = Some(system.jac_symmetry); + let mut jj = CooMatrix::new(system.ndim, system.ndim, system.jac_nnz, symmetry).unwrap(); + (system.jacobian)(&mut jj, data.x0, &data.y0, multiplier, &mut args).unwrap(); + + // compute the numerical Jacobian matrix + let num = numerical_jacobian(system.ndim, data.x0, data.y0, system.function, multiplier); + + // check the Jacobian matrix + let ana = jj.as_dense(); + println!("{:.15}", ana); + println!("{:.15}", num); + mat_approx_eq(&ana, &num, 1e-13); + + // check the mass matrix + let mass = system.mass_matrix.unwrap(); + println!("{}", mass.as_dense()); + let ndim = system.ndim; + let nnz_mass = 5 + 4; + assert_eq!(mass.get_info(), (ndim, ndim, nnz_mass, Symmetry::No)); + } +} diff --git a/russell_ode/src/system.rs b/russell_ode/src/system.rs new file mode 100644 index 00000000..2edd5ed4 --- /dev/null +++ b/russell_ode/src/system.rs @@ -0,0 +1,324 @@ +use crate::{HasJacobian, StrError}; +use russell_lab::Vector; +use russell_sparse::{CooMatrix, Symmetry}; +use std::marker::PhantomData; + +/// Indicates that the system functions do not require extra arguments +pub type NoArgs = u8; + +/// Defines a system of first order ordinary differential equations (ODE) or a differential-algebraic system (DAE) of Index-1 +/// +/// The system is defined by: +/// +/// ```text +/// d{y} +/// [M] ———— = {f}(x, {y}) +/// dx +/// ``` +/// +/// where `x` is the independent scalar variable (e.g., time), `{y}` is the solution vector, +/// `{f}` is the right-hand side vector, and `[M]` is the so-called "mass matrix". +/// +/// **Note:** The mass matrix is optional and need not be specified. +/// +/// The Jacobian is defined by: +/// +/// ```text +/// ∂{f} +/// [J](x, {y}) = ———— +/// ∂{y} +/// ``` +/// +/// where `[J]` is the Jacobian matrix. +/// +/// # Generics +/// +/// The generic arguments here are: +/// +/// * `F` -- function to compute the `f` vector: `(f: &mut Vector, x: f64, y: &Vector, args: &mut A)` +/// * `J` -- function to compute the Jacobian: `(jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut A)` +/// * `A` -- generic argument to assist in the `F` and `J` functions. It may be simply the [NoArgs] type indicating that no arguments are needed. +/// +/// # Important +/// +/// The internal implementation requires that the `multiplier` parameter in +/// the Jacobian function `J` be used used to scale the Jacobian matrix. For example: +/// +/// ```text +/// |jj: &mut CooMatrix, x: f64, y: &Vector, multiplier: f64, args: &mut Args| { +/// jj.reset(); +/// jj.put(0, 0, multiplier * LAMBDA)?; +/// Ok(()) +/// }, +/// ``` +pub struct System +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// System dimension + pub(crate) ndim: usize, + + /// ODE system function + pub(crate) function: F, + + /// Jacobian function + pub(crate) jacobian: J, + + /// Indicates whether the analytical Jacobian is available or not + pub(crate) jac_available: bool, + + /// Number of non-zeros in the Jacobian matrix + pub(crate) jac_nnz: usize, + + /// Symmetry properties of the Jacobian matrix + pub(crate) jac_symmetry: Symmetry, + + /// Holds the mass matrix + pub(crate) mass_matrix: Option, + + /// Handle generic argument + phantom: PhantomData A>, +} + +impl<'a, F, J, A> System +where + F: Send + Fn(&mut Vector, f64, &Vector, &mut A) -> Result<(), StrError>, + J: Send + Fn(&mut CooMatrix, f64, &Vector, f64, &mut A) -> Result<(), StrError>, +{ + /// Allocates a new instance + /// + /// # Input + /// + /// * `ndim` -- dimension of the ODE system (number of equations) + /// * `function` -- implements the function: `dy/dx = f(x, y)` + /// * `jacobian` -- implements the Jacobian: `J = df/dy` + /// * `has_jacobian` -- indicates that the analytical Jacobian is available (input by `jacobian`) + /// * `jac_nnz` -- the number of non-zeros in the Jacobian; use None to indicate a full matrix (i.e., nnz = ndim * ndim) + /// * `jac_symmetry` -- specifies the type of symmetry representation for the Jacobian matrix + /// + /// # Generics + /// + /// See [System] for an explanation of the generic parameters. + pub fn new( + ndim: usize, + function: F, + jacobian: J, + has_ana_jacobian: HasJacobian, + jac_nnz: Option, + jac_symmetry: Option, + ) -> Self { + let jac_available = match has_ana_jacobian { + HasJacobian::Yes => true, + HasJacobian::No => false, + }; + System { + ndim, + function, + jacobian, + jac_available, + jac_nnz: if let Some(n) = jac_nnz { n } else { ndim * ndim }, + jac_symmetry: if let Some(s) = jac_symmetry { s } else { Symmetry::No }, + mass_matrix: None, + phantom: PhantomData, + } + } + + /// Initializes and enables the mass matrix + /// + /// **Note:** Later, call [System::mass_put] to "put" elements in the mass matrix. + /// + /// # Input + /// + /// * `max_nnz` -- Max number of non-zero values + pub fn init_mass_matrix(&mut self, max_nnz: usize) -> Result<(), StrError> { + let sym = if self.jac_symmetry == Symmetry::No { + None + } else { + Some(self.jac_symmetry) + }; + self.mass_matrix = Some(CooMatrix::new(self.ndim, self.ndim, max_nnz, sym).unwrap()); + Ok(()) + } + + /// Puts a new element in the mass matrix (duplicates allowed) + /// + /// See also [russell_sparse::CooMatrix::put]. + /// + /// # Input + /// + /// * `i` -- row index (indices start at zero; zero-based) + /// * `j` -- column index (indices start at zero; zero-based) + /// * `value` -- the value M(i,j) + pub fn mass_put(&mut self, i: usize, j: usize, value: f64) -> Result<(), StrError> { + match self.mass_matrix.as_mut() { + Some(mass) => mass.put(i, j, value), + None => Err("mass matrix has not been initialized/enabled"), + } + } + + /// Returns the dimension of the ODE system + pub fn get_ndim(&self) -> usize { + self.ndim + } + + /// Computes the numerical Jacobian + /// + /// ```text + /// ∂{f} Δfᵢ + /// ———— = [J](x, {y}) Jᵢⱼ ≈ ——— + /// ∂{y} Δyⱼ + /// ``` + /// + /// **Note:** Will call `function` ndim times. + pub(crate) fn numerical_jacobian( + &self, + jj: &mut CooMatrix, + x: f64, + y: &mut Vector, + fxy: &Vector, + multiplier: f64, + args: &mut A, + aux: &mut Vector, + ) -> Result<(), StrError> { + assert_eq!(aux.dim(), self.ndim); + const THRESHOLD: f64 = 1e-5; + jj.reset(); + for j in 0..self.ndim { + let yj_original = y[j]; // create copy + let delta_yj = f64::sqrt(f64::EPSILON * f64::max(THRESHOLD, f64::abs(y[j]))); + y[j] += delta_yj; // y[j] := y[j] + Δy + (self.function)(aux, x, y, args)?; // work := f(x, y + Δy) + for i in 0..self.ndim { + let delta_fi = aux[i] - fxy[i]; // compute Δf[..] + jj.put(i, j, multiplier * delta_fi / delta_yj).unwrap(); // Δfi/Δfj + } + y[j] = yj_original; // restore value + } + Ok(()) + } +} + +/// Implements a placeholder function for when the analytical Jacobian is unavailable +/// +/// **Note:** Use this function with the [crate::HasJacobian::No] option. +pub fn no_jacobian( + _jj: &mut CooMatrix, + _x: f64, + _y: &Vector, + _multiplier: f64, + _args: &mut A, +) -> Result<(), StrError> { + Err("analytical Jacobian is not available") +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::{no_jacobian, System}; + use crate::HasJacobian; + use russell_lab::Vector; + use russell_sparse::CooMatrix; + + #[test] + fn ode_system_most_none_works() { + struct Args { + n_function_eval: usize, + more_data_goes_here: bool, + } + let mut args = Args { + n_function_eval: 0, + more_data_goes_here: false, + }; + let ode = System::new( + 2, + |f, x, y, args: &mut Args| { + args.n_function_eval += 1; + f[0] = -x * y[1]; + f[1] = x * y[0]; + args.more_data_goes_here = true; + Ok(()) + }, + no_jacobian, + HasJacobian::No, + None, + None, + ); + // call system function + let x = 0.0; + let y = Vector::new(2); + let mut k = Vector::new(2); + (ode.function)(&mut k, x, &y, &mut args).unwrap(); + // call jacobian function + let mut jj = CooMatrix::new(2, 2, 2, None).unwrap(); + let m = 1.0; + assert_eq!( + (ode.jacobian)(&mut jj, x, &y, m, &mut args), + Err("analytical Jacobian is not available") + ); + // check + println!("n_function_eval = {}", args.n_function_eval); + assert_eq!(args.n_function_eval, 1); + assert_eq!(args.more_data_goes_here, true); + } + + #[test] + fn ode_system_some_none_works() { + struct Args { + n_function_eval: usize, + n_jacobian_eval: usize, + more_data_goes_here_fn: bool, + more_data_goes_here_jj: bool, + } + let mut args = Args { + n_function_eval: 0, + n_jacobian_eval: 0, + more_data_goes_here_fn: false, + more_data_goes_here_jj: false, + }; + let mut ode = System::new( + 2, + |f, x, y, args: &mut Args| { + args.n_function_eval += 1; + f[0] = -x * y[1]; + f[1] = x * y[0]; + args.more_data_goes_here_fn = true; + Ok(()) + }, + |jj, x, _y, _multiplier, args: &mut Args| { + args.n_jacobian_eval += 1; + jj.reset(); + jj.put(0, 1, -x).unwrap(); + jj.put(1, 0, x).unwrap(); + args.more_data_goes_here_jj = true; + Ok(()) + }, + HasJacobian::Yes, + Some(2), + None, + ); + // analytical_solution: + // y[0] = f64::cos(x * x / 2.0) - 2.0 * f64::sin(x * x / 2.0); + // y[1] = 2.0 * f64::cos(x * x / 2.0) + f64::sin(x * x / 2.0); + ode.init_mass_matrix(2).unwrap(); // diagonal mass matrix => OK, but not needed + ode.mass_put(0, 0, 1.0).unwrap(); + ode.mass_put(1, 1, 1.0).unwrap(); + // call system function + let x = 0.0; + let y = Vector::new(2); + let mut k = Vector::new(2); + (ode.function)(&mut k, x, &y, &mut args).unwrap(); + // call jacobian function + let mut jj = CooMatrix::new(2, 2, 2, None).unwrap(); + let m = 1.0; + (ode.jacobian)(&mut jj, x, &y, m, &mut args).unwrap(); + // check + println!("n_function_eval = {}", args.n_function_eval); + println!("n_jacobian_eval = {}", args.n_jacobian_eval); + assert_eq!(args.n_function_eval, 1); + assert_eq!(args.n_jacobian_eval, 1); + assert_eq!(args.more_data_goes_here_fn, true); + assert_eq!(args.more_data_goes_here_jj, true); + } +} diff --git a/russell_ode/src/workspace.rs b/russell_ode/src/workspace.rs new file mode 100644 index 00000000..afe0f596 --- /dev/null +++ b/russell_ode/src/workspace.rs @@ -0,0 +1,76 @@ +use crate::{Benchmark, Method}; + +/// Holds workspace data shared among the ODE solver and actual implementations +pub(crate) struct Workspace { + /// Holds benchmark data + pub(crate) bench: Benchmark, + + /// Indicates that the step follows a reject + pub(crate) follows_reject_step: bool, + + /// Indicates that the iterations (in an implicit method) are diverging + pub(crate) iterations_diverging: bool, + + /// Holds a multiplier to the stepsize when the iterations are diverging + pub(crate) h_multiplier_diverging: f64, + + /// Holds the previous stepsize + pub(crate) h_prev: f64, + + /// Holds the next stepsize estimate + pub(crate) h_new: f64, + + /// Holds the previous relative error + pub(crate) rel_error_prev: f64, + + /// Holds the current relative error + pub(crate) rel_error: f64, + + /// Holds the h times lambda coefficient to detect stiffness + pub(crate) stiff_h_times_lambda: f64, + + /// Holds the number of negative stiffness detections + pub(crate) stiff_n_detection_no: usize, + + /// Holds the number of positive stiffness detections + pub(crate) stiff_n_detection_yes: usize, + + /// Indicates whether stiffness has been detected or not (after some "yes" steps have been found) + pub(crate) stiff_detected: bool, +} + +impl Workspace { + /// Allocates a new instance + pub(crate) fn new(method: Method) -> Self { + Workspace { + bench: Benchmark::new(method), + follows_reject_step: false, + iterations_diverging: false, + h_multiplier_diverging: 1.0, + h_prev: 0.0, + h_new: 0.0, + rel_error_prev: 0.0, + rel_error: 0.0, + stiff_h_times_lambda: 0.0, + stiff_n_detection_no: 0, + stiff_n_detection_yes: 0, + stiff_detected: false, + } + } + + /// Resets all values + pub(crate) fn reset(&mut self, h: f64, rel_error_prev_min: f64) { + self.bench.reset(h); + self.follows_reject_step = false; + self.iterations_diverging = false; + self.h_multiplier_diverging = 1.0; + self.h_prev = h; + self.h_new = h; + self.rel_error_prev = rel_error_prev_min; + self.rel_error = 0.0; + self.stiff_h_times_lambda = 0.0; + self.stiff_n_detection_no = 0; + self.stiff_n_detection_yes = 0; + self.stiff_detected = false; + } +} diff --git a/russell_ode/tests/test_bweuler.rs b/russell_ode/tests/test_bweuler.rs new file mode 100644 index 00000000..f29e2d4b --- /dev/null +++ b/russell_ode/tests/test_bweuler.rs @@ -0,0 +1,127 @@ +use russell_lab::{approx_eq, Vector}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_bweuler_hairer_wanner_eq1() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let params = Params::new(Method::BwEuler); + let mut solver = OdeSolver::new(params, &system).unwrap(); + + // solve the ODE system + solver + .solve(&mut data.y0, data.x0, data.x1, data.h_equal, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with a previous implementation + approx_eq(data.y0[0], 0.09060476604187756, 1e-15); + assert_eq!(stat.h_accepted, data.h_equal.unwrap()); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 5e-5); + + // print and check statistics + println!("{}", stat); + assert_eq!(stat.n_function, 80); + assert_eq!(stat.n_jacobian, 40); + assert_eq!(stat.n_factor, 40); + assert_eq!(stat.n_lin_sol, 40); + assert_eq!(stat.n_steps, 40); + assert_eq!(stat.n_accepted, 40); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 2); + assert_eq!(stat.n_iterations_max, 2); +} + +#[test] +fn test_bweuler_hairer_wanner_eq1_num_jac() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let mut params = Params::new(Method::BwEuler); + params.newton.use_numerical_jacobian = true; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, data.h_equal, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with a previous implementation + approx_eq(data.y0[0], 0.09060476598021044, 1e-11); + assert_eq!(stat.h_accepted, data.h_equal.unwrap()); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 5e-5); + + // print and check statistics + println!("{}", stat); + assert_eq!(stat.n_function, 120); + assert_eq!(stat.n_jacobian, 40); + assert_eq!(stat.n_factor, 40); + assert_eq!(stat.n_lin_sol, 40); + assert_eq!(stat.n_steps, 40); + assert_eq!(stat.n_accepted, 40); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 2); + assert_eq!(stat.n_iterations_max, 2); +} + +#[test] +fn test_bweuler_hairer_wanner_eq1_modified_newton() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let mut params = Params::new(Method::BwEuler); + params.bweuler.use_modified_newton = true; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, data.h_equal, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with a previous implementation + approx_eq(data.y0[0], 0.09060476604187756, 1e-15); + assert_eq!(stat.h_accepted, data.h_equal.unwrap()); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 5e-5); + + // print and check statistics + println!("{}", stat); + assert_eq!(stat.n_function, 80); + assert_eq!(stat.n_jacobian, 1); + assert_eq!(stat.n_factor, 1); + assert_eq!(stat.n_lin_sol, 40); + assert_eq!(stat.n_steps, 40); + assert_eq!(stat.n_accepted, 40); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 2); + assert_eq!(stat.n_iterations_max, 2); +} diff --git a/russell_ode/tests/test_dopri5_arenstorf.rs b/russell_ode/tests/test_dopri5_arenstorf.rs new file mode 100644 index 00000000..b7ec14bc --- /dev/null +++ b/russell_ode/tests/test_dopri5_arenstorf.rs @@ -0,0 +1,62 @@ +use russell_lab::{approx_eq, format_fortran}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_dopri5_arenstorf() { + // get ODE system + let (system, mut data, mut args) = Samples::arenstorf(); + + // set configuration parameters + let mut params = Params::new(Method::DoPri5); + params.step.h_ini = 1e-4; + params.set_tolerances(1e-7, 1e-7, None).unwrap(); + + // enable dense output with 1.0 spacing + let mut out = Output::new(); + out.enable_dense(1.0, &[0, 1, 2, 3]).unwrap(); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with dopri5.f + approx_eq(data.y0[0], 9.940021704030663E-01, 1e-11); + approx_eq(data.y0[1], 9.040891036151961E-06, 1e-11); + approx_eq(data.y0[2], 1.459758305600828E-03, 1e-9); + approx_eq(data.y0[3], -2.001245515834718E+00, 1e-9); + approx_eq(stat.h_accepted, 5.258587607119909E-04, 1e-10); + + // print dense output + let n_dense = out.dense_step_index.len(); + for i in 0..n_dense { + println!( + "step ={:>4}, x ={:6.2}, y ={}{}{}{}", + out.dense_step_index[i], + out.dense_x[i], + format_fortran(out.dense_y.get(&0).unwrap()[i]), + format_fortran(out.dense_y.get(&1).unwrap()[i]), + format_fortran(out.dense_y.get(&2).unwrap()[i]), + format_fortran(out.dense_y.get(&3).unwrap()[i]), + ); + } + + // print and check statistics + println!("{}", stat.summary()); + println!( + "y ={}{}{}{}", + format_fortran(data.y0[0]), + format_fortran(data.y0[1]), + format_fortran(data.y0[2]), + format_fortran(data.y0[3]) + ); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 1429); + assert_eq!(stat.n_steps, 238); + assert_eq!(stat.n_accepted, 217); + assert_eq!(stat.n_rejected, 21); +} diff --git a/russell_ode/tests/test_dopri5_arenstorf_debug.rs b/russell_ode/tests/test_dopri5_arenstorf_debug.rs new file mode 100644 index 00000000..946c7794 --- /dev/null +++ b/russell_ode/tests/test_dopri5_arenstorf_debug.rs @@ -0,0 +1,45 @@ +use russell_lab::{approx_eq, format_fortran}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_dopri5_arenstorf_debug() { + // get ODE system + let (system, mut data, mut args) = Samples::arenstorf(); + + // set configuration parameters + let mut params = Params::new(Method::DoPri5); + params.step.h_ini = 1e-4; + params.set_tolerances(1e-7, 1e-7, None).unwrap(); + params.debug = true; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with dopri5.f + approx_eq(data.y0[0], 9.940021704030663E-01, 1e-11); + approx_eq(data.y0[1], 9.040891036151961E-06, 1e-11); + approx_eq(data.y0[2], 1.459758305600828E-03, 1e-9); + approx_eq(data.y0[3], -2.001245515834718E+00, 1e-9); + approx_eq(stat.h_accepted, 5.258587607119909E-04, 1e-10); + + // print and check statistics + println!("{}", stat.summary()); + println!( + "y ={}{}{}{}", + format_fortran(data.y0[0]), + format_fortran(data.y0[1]), + format_fortran(data.y0[2]), + format_fortran(data.y0[3]) + ); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 1429); + assert_eq!(stat.n_steps, 238); + assert_eq!(stat.n_accepted, 217); + assert_eq!(stat.n_rejected, 21); +} diff --git a/russell_ode/tests/test_dopri5_hairer_wanner_eq1.rs b/russell_ode/tests/test_dopri5_hairer_wanner_eq1.rs new file mode 100644 index 00000000..d9cb5433 --- /dev/null +++ b/russell_ode/tests/test_dopri5_hairer_wanner_eq1.rs @@ -0,0 +1,55 @@ +use russell_lab::{approx_eq, format_fortran, Vector}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_dopri5_hairer_wanner_eq1() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let mut params = Params::new(Method::DoPri5); + params.step.h_ini = 1e-4; + + // enable dense output with 0.1 spacing + let mut out = Output::new(); + out.enable_dense(0.1, &[0]).unwrap(); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with dopri5.f + approx_eq(data.y0[0], 9.063921649310544E-02, 1e-13); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 4e-5); + + // print dense output + let n_dense = out.dense_step_index.len(); + for i in 0..n_dense { + println!( + "step ={:>4}, x ={:6.2}, y ={}", + out.dense_step_index[i], + out.dense_x[i], + format_fortran(out.dense_y.get(&0).unwrap()[i]), + ); + } + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}", format_fortran(data.y0[0])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 235); + assert_eq!(stat.n_steps, 39); + assert_eq!(stat.n_accepted, 39); + assert_eq!(stat.n_rejected, 0); +} diff --git a/russell_ode/tests/test_dopri5_van_der_pol_debug.rs b/russell_ode/tests/test_dopri5_van_der_pol_debug.rs new file mode 100644 index 00000000..4ff4b59e --- /dev/null +++ b/russell_ode/tests/test_dopri5_van_der_pol_debug.rs @@ -0,0 +1,54 @@ +use russell_lab::{approx_eq, format_fortran, vec_approx_eq, Vector}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_dopri5_van_der_pol_debug() { + // get get ODE system + const EPS: f64 = 0.003; + let (system, _, mut args) = Samples::van_der_pol(Some(EPS), false); + + // set configuration parameters + let mut params = Params::new(Method::DoPri5); + params.step.h_ini = 1e-4; + params.set_tolerances(1e-3, 1e-3, None).unwrap(); + params.debug = true; + params.stiffness.skip_first_n_accepted_step = 0; + params.stiffness.enabled = true; + params.stiffness.stop_with_error = false; + params.stiffness.save_results = true; + + // output (to save stiff stations) + let mut out = Output::new(); + + // solve the ODE system + let mut y0 = Vector::from(&[2.0, 0.0]); + let x0 = 0.0; + let x1 = 2.0; + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver.solve(&mut y0, x0, x1, None, Some(&mut out), &mut args).unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with dopri5.f + approx_eq(y0[0], 1.820788982019278E+00, 1e-12); + approx_eq(y0[1], -7.853646714272298E-01, 1e-12); + approx_eq(stat.h_accepted, 4.190371271724428E-03, 1e-13); + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(y0[0]), format_fortran(y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 2558 - 1); // -1 when compared with dopri5.f + assert_eq!(stat.n_steps, 426); + assert_eq!(stat.n_accepted, 406); + assert_eq!(stat.n_rejected, 20); + + // check stiffness results + assert_eq!(out.stiff_step_index, &[32, 189, 357]); + vec_approx_eq( + &out.stiff_x, + &[8.973510130811428E-02, 9.306509118845370E-01, 1.809882994363079E+00], + 1e-12, + ); +} diff --git a/russell_ode/tests/test_dopri8_van_der_pol.rs b/russell_ode/tests/test_dopri8_van_der_pol.rs new file mode 100644 index 00000000..20adcb59 --- /dev/null +++ b/russell_ode/tests/test_dopri8_van_der_pol.rs @@ -0,0 +1,54 @@ +use russell_lab::{approx_eq, format_fortran, Vector}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_dopri8_van_der_pol() { + // get get ODE system + const EPS: f64 = 1e-3; + let (system, _, mut args) = Samples::van_der_pol(Some(EPS), false); + + // set configuration parameters + let mut params = Params::new(Method::DoPri8); + params.step.h_ini = 1e-6; + params.set_tolerances(1e-9, 1e-9, None).unwrap(); + + // enable dense output with 0.2 spacing + let mut out = Output::new(); + out.enable_dense(0.1, &[0, 1]).unwrap(); + + // solve the ODE system + let mut y0 = Vector::from(&[2.0, 0.0]); + let x0 = 0.0; + let x1 = 2.0; + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver.solve(&mut y0, x0, x1, None, Some(&mut out), &mut args).unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with dop853.f + approx_eq(y0[0], 1.763234540172087E+00, 1e-13); + approx_eq(y0[1], -8.356886819301910E-01, 1e-12); + approx_eq(stat.h_accepted, 8.656983588595286E-04, 4.5e-7); + + // print dense output + let n_dense = out.dense_step_index.len(); + for i in 0..n_dense { + println!( + "step ={:>4}, x ={:5.2}, y ={}{}", + out.dense_step_index[i], + out.dense_x[i], + format_fortran(out.dense_y.get(&0).unwrap()[i]), + format_fortran(out.dense_y.get(&1).unwrap()[i]), + ); + } + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(y0[0]), format_fortran(y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 21553 - 2); // -2 when compared with dop853 + assert_eq!(stat.n_steps, 1469); + assert_eq!(stat.n_accepted, 1348); + assert_eq!(stat.n_rejected, 121); +} diff --git a/russell_ode/tests/test_dopri8_van_der_pol_debug.rs b/russell_ode/tests/test_dopri8_van_der_pol_debug.rs new file mode 100644 index 00000000..51f056fa --- /dev/null +++ b/russell_ode/tests/test_dopri8_van_der_pol_debug.rs @@ -0,0 +1,55 @@ +use russell_lab::{approx_eq, format_fortran, vec_approx_eq, Vector}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_dopri8_van_der_pol_debug() { + // get get ODE system + const EPS: f64 = 0.003; + let (system, _, mut args) = Samples::van_der_pol(Some(EPS), false); + + // set configuration parameters + let mut params = Params::new(Method::DoPri8); + params.step.h_ini = 1e-4; + params.set_tolerances(1e-3, 1e-3, None).unwrap(); + params.debug = true; + params.stiffness.skip_first_n_accepted_step = 0; + params.stiffness.enabled = true; + params.stiffness.stop_with_error = false; + params.stiffness.save_results = true; + + // output (to save stiff stations) + let mut out = Output::new(); + + // solve the ODE system + let mut y0 = Vector::from(&[2.0, 0.0]); + let x0 = 0.0; + let x1 = 2.0; + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver.solve(&mut y0, x0, x1, None, Some(&mut out), &mut args).unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with dop853.f + approx_eq(y0[0], 1.819907445729370E+00, 1e-9); + approx_eq(y0[1], -7.866363461162956E-01, 1e-8); + approx_eq(stat.h_accepted, 6.908420682852039E-03, 1e-8); + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(y0[0]), format_fortran(y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + let n_extra = stat.n_accepted; // because dopri8 calls the function in the stiffness detection code + assert_eq!(stat.n_function, 2802 - 2 + n_extra); // -2 when compared with dop853 + assert_eq!(stat.n_steps, 235); + assert_eq!(stat.n_accepted, 215); + assert_eq!(stat.n_rejected, 20); + + // check stiffness results + assert_eq!(out.stiff_step_index, &[21, 109, 196]); + vec_approx_eq( + &out.stiff_x, + &[1.141460201067603E-01, 9.881349085950795E-01, 1.853542594920877E+00], + 1e-8, + ); +} diff --git a/russell_ode/tests/test_fweuler.rs b/russell_ode/tests/test_fweuler.rs new file mode 100644 index 00000000..41100283 --- /dev/null +++ b/russell_ode/tests/test_fweuler.rs @@ -0,0 +1,43 @@ +use russell_lab::{approx_eq, Vector}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_fweuler_hairer_wanner_eq1() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let params = Params::new(Method::FwEuler); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, data.h_equal, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with a previous implementation + approx_eq(data.y0[0], 0.08589790706616637, 1e-15); + assert_eq!(stat.h_accepted, data.h_equal.unwrap()); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 0.004753); + + // print and check statistics + println!("{}", stat); + assert_eq!(stat.n_function, 40); + assert_eq!(stat.n_jacobian, 0); + assert_eq!(stat.n_factor, 0); + assert_eq!(stat.n_lin_sol, 0); + assert_eq!(stat.n_steps, 40); + assert_eq!(stat.n_accepted, 40); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 0); + assert_eq!(stat.n_iterations_max, 0); +} diff --git a/russell_ode/tests/test_mdeuler.rs b/russell_ode/tests/test_mdeuler.rs new file mode 100644 index 00000000..4a5b0f71 --- /dev/null +++ b/russell_ode/tests/test_mdeuler.rs @@ -0,0 +1,43 @@ +use russell_lab::{approx_eq, Vector}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_mdeuler_hairer_wanner_eq1() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let mut params = Params::new(Method::MdEuler); + params.step.h_ini = 1e-4; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with a previous implementation + approx_eq(data.y0[0], 0.09062475637905158, 1e-16); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 1e-4); + + // print and check statistics + println!("{}", stat); + assert_eq!(stat.n_function, 424); + assert_eq!(stat.n_jacobian, 0); + assert_eq!(stat.n_factor, 0); + assert_eq!(stat.n_lin_sol, 0); + assert_eq!(stat.n_steps, 212); + assert_eq!(stat.n_accepted, 212); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 0); + assert_eq!(stat.n_iterations_max, 0); +} diff --git a/russell_ode/tests/test_radau5_amplifier1t.rs b/russell_ode/tests/test_radau5_amplifier1t.rs new file mode 100644 index 00000000..6f062639 --- /dev/null +++ b/russell_ode/tests/test_radau5_amplifier1t.rs @@ -0,0 +1,216 @@ +use russell_lab::{approx_eq, format_fortran, format_scientific}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_radau5_amplifier1t() { + // get get ODE system + let (system, mut data, mut args) = Samples::amplifier1t(); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-6; + params.set_tolerances(1e-4, 1e-4, None).unwrap(); + + // enable output of accepted steps + let mut out = Output::new(); + out.enable_dense(0.001, &[0, 4]).unwrap(); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], -2.226517868073645E-02, 1e-10); + approx_eq(data.y0[1], 3.068700099735197E+00, 1e-10); + approx_eq(data.y0[2], 2.898340496450958E+00, 1e-9); + approx_eq(data.y0[3], 2.033525366489690E+00, 1e-7); + approx_eq(data.y0[4], -2.269179823457655E+00, 1e-7); + approx_eq(stat.h_accepted, 7.791381954171996E-04, 1e-6); + + // compare dense output with Mathematica + let n_dense = out.dense_step_index.len(); + for i in 0..n_dense { + approx_eq(out.dense_x[i], X_MATH[i], 1e-15); + let diff0 = f64::abs(out.dense_y.get(&0).unwrap()[i] - Y0_MATH[i]); + let diff4 = f64::abs(out.dense_y.get(&4).unwrap()[i] - Y4_MATH[i]); + println!( + "step ={:>4}, x ={:7.4}, y1and5 ={}{}, diff1and5 ={}{}", + out.dense_step_index[i], + out.dense_x[i], + format_fortran(out.dense_y.get(&0).unwrap()[i]), + format_fortran(out.dense_y.get(&4).unwrap()[i]), + format_scientific(diff0, 8, 1), + format_scientific(diff4, 8, 1) + ); + assert!(diff0 < 1e-4); + assert!(diff4 < 1e-3); + } + + // print and check statistics + println!("{}", stat.summary()); + println!( + "y1to3 ={}{}{}", + format_fortran(data.y0[0]), + format_fortran(data.y0[1]), + format_fortran(data.y0[2]), + ); + println!("y4to5 ={}{}", format_fortran(data.y0[3]), format_fortran(data.y0[4])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 1511); + assert_eq!(stat.n_jacobian, 126); + assert_eq!(stat.n_factor, 166); + assert_eq!(stat.n_lin_sol, 461); + assert_eq!(stat.n_steps, 166); + assert_eq!(stat.n_accepted, 127); + assert_eq!(stat.n_rejected, 6); + assert_eq!(stat.n_iterations_max, 5); +} + +// Mathematica code +// eqs = { +// Ue[t]/Subscript[R, 0] - U1[t]/Subscript[R, 0] + Subscript[C, 1] (U2'[t] - U1'[t]) == 0, +// Ub/Subscript[R, 2] - U2[t] (1/Subscript[R, 1] + 1/Subscript[R, 2]) + Subscript[C, 1] (U1'[t] - U2'[t]) - (1 - \[Alpha]) f[U2[t] - U3[t]] == 0, +// f[U2[t] - U3[t]] - U3[t]/Subscript[R, 3] - Subscript[C, 2] U3'[t] == 0, +// Ub/Subscript[R, 4] - U4[t]/Subscript[R, 4] + Subscript[C, 3] (U5'[t] - U4'[t]) - \[Alpha] f[U2[t] - U3[t]] == 0, +// -(U5[t]/Subscript[R, 5]) + Subscript[C, 3] (U4'[t] - U5'[t]) == 0 +// }; +// ini = { +// U1[0] == 0, +// U2[0] == (Ub Subscript[R, 1])/(Subscript[R, 1] + Subscript[R, 2]), +// U3[0] == (Ub Subscript[R, 1])/(Subscript[R, 1] + Subscript[R, 2]), +// U4[0] == Ub, +// U5[0] == 0 +// }; +// sub1 = {Ue[t] -> A Sin[\[Omega] t], f[U2[t] - U3[t]] -> \[Beta] (Exp[(U2[t] - U3[t])/Uf] - 1)}; +// sub2 = {Subscript[R, 0] -> R, Subscript[R, 1] -> S, Subscript[R, 2] -> S, Subscript[R, 3] -> S, Subscript[R, 4] -> S, Subscript[R, 5] -> S}; +// sub3 = {\[Alpha] -> 0.99, \[Beta] -> 10^-6, A -> 0.4, \[Omega] -> 200 \[Pi], Ub -> 6, Uf -> 0.026, R -> 1000, S -> 9000}; +// sub4 = {Subscript[C, 1] -> 1 10^-6, Subscript[C, 2] -> 2 10^-6, Subscript[C, 3] -> 3 10^-6}; +// DAE = {eqs, ini} /. sub1 /. sub2 /. sub3 /. sub4; +// tMax = 0.05; +// sol = NDSolve[DAE, {U1, U2, U3, U4, U5}, {t, 0, tMax}, Method -> {"EquationSimplification" -> "Residual"}] +// xx = Table[0.001 k, {k, 0, 50}]; +// yy1 = Table[Evaluate[U1[0.001 k] /. sol][[1]], {k, 0, 50}]; +// yy5 = Table[Evaluate[U5[0.001 k] /. sol][[1]], {k, 0, 50}]; +// NumberForm[xx, 10] +// NumberForm[yy1, 20] +// NumberForm[yy5, 20] + +const X_MATH: [f64; 51] = [ + 0.0, 0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008, 0.009, 0.01, 0.011, 0.012, 0.013, 0.014, 0.015, 0.016, + 0.017, 0.018, 0.019, 0.02, 0.021, 0.022, 0.023, 0.024, 0.025, 0.026, 0.027, 0.028, 0.029, 0.03, 0.031, 0.032, + 0.033, 0.034, 0.035, 0.036, 0.037, 0.038, 0.039, 0.04, 0.041, 0.042, 0.043, 0.044, 0.045, 0.046, 0.047, 0.048, + 0.049, 0.05, +]; + +const Y0_MATH: [f64; 51] = [ + 0.0, + 0.19163123318374464, + 0.32185367879057564, + 0.3335960606551303, + 0.2215635695367279, + 0.028220795080320382, + -0.17260350843054062, + -0.3052891397454663, + -0.3208689996160992, + -0.21128708362065052, + -0.019646365460276864, + 0.18055684137277278, + 0.3126183722955318, + 0.3258766828442236, + 0.2151028597022245, + 0.022815953071239736, + -0.17710807778952675, + -0.3089263949913023, + -0.32400009272978636, + -0.21390596328872188, + -0.02183398021705521, + 0.17872956293627157, + 0.3110919501345503, + 0.32460094432393466, + 0.21403514145654773, + 0.021922703389574156, + -0.17785264281824917, + -0.3095276078905207, + -0.3245174337185357, + -0.21433875579866524, + -0.0221955020760429, + 0.178427591579212, + 0.31083968403700063, + 0.32439008481420134, + 0.2138585580647758, + 0.021774982739371514, + -0.17797577774000792, + -0.30962704584498146, + -0.3246029836040378, + -0.21441032868740578, + -0.022255288051796426, + 0.1783776539665792, + 0.31079792296298414, + 0.3243550120745409, + 0.213829224711314, + 0.021750447911481622, + -0.17799622886350802, + -0.3096435634057654, + -0.32461719161580643, + -0.21442221448662638, + -0.022265217137233992, +]; + +const Y4_MATH: [f64; 51] = [ + 0.0, + -2.688717471445241, + -1.744875771519701, + -0.7595061649951028, + -0.1395497865888305, + 0.05034402841100122, + 0.06067077896330943, + -0.407284001780353, + -2.00356303745131, + -2.77121845975808, + -2.909034623992957, + -2.3988776721015097, + -1.4609137736344042, + -0.49209021995023644, + 0.10853093997216126, + 0.2829974543994493, + 0.28987804919359145, + -0.11445233279337079, + -1.7735050363366236, + -2.5504062030565406, + -2.6947100730211817, + -2.1904643464620728, + -1.2580259788765373, + -0.29482512838806, + 0.2996980482820194, + 0.4688423063296886, + 0.47239083114525215, + 0.0758904784124937, + -1.5959310434406633, + -2.3769733662474115, + -2.5248624462491884, + -2.0240604680324514, + -1.0949668210442012, + -0.1350714004629226, + 0.45611288978154885, + 0.6220924827404174, + 0.6228408471704201, + 0.22542205043554753, + -1.45066522572926, + -2.234520653243942, + -2.3850899205464375, + -1.886906532659037, + -0.9604028944163061, + -0.00304971461906603, + 0.5856876627762617, + 0.7492431432249377, + 0.7476605884948642, + 0.3482562599754222, + -1.330336048141704, + -2.116423719696165, + -2.2691710591230145, +]; diff --git a/russell_ode/tests/test_radau5_hairer_wanner_eq1.rs b/russell_ode/tests/test_radau5_hairer_wanner_eq1.rs new file mode 100644 index 00000000..cd0eb3d3 --- /dev/null +++ b/russell_ode/tests/test_radau5_hairer_wanner_eq1.rs @@ -0,0 +1,61 @@ +use russell_lab::{approx_eq, format_fortran, Vector}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_radau5_hairer_wanner_eq1() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-4; + + // enable dense output with 0.2 spacing + let mut out = Output::new(); + out.enable_dense(0.2, &[0]).unwrap(); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], 9.068021382386648E-02, 1e-15); + approx_eq(stat.h_accepted, 1.272673814374611E+00, 1e-12); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 3e-5); + + // print dense output + let n_dense = out.dense_step_index.len(); + for i in 0..n_dense { + println!( + "step ={:>4}, x ={:5.2}, y ={}", + out.dense_step_index[i], + out.dense_x[i], + format_fortran(out.dense_y.get(&0).unwrap()[i]) + ); + } + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}", format_fortran(data.y0[0])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 67); + assert_eq!(stat.n_jacobian, 1); + assert_eq!(stat.n_factor, 13); + assert_eq!(stat.n_lin_sol, 17); + assert_eq!(stat.n_steps, 15); + assert_eq!(stat.n_accepted, 15); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 1); + assert_eq!(stat.n_iterations_max, 2); +} diff --git a/russell_ode/tests/test_radau5_hairer_wanner_eq1_debug.rs b/russell_ode/tests/test_radau5_hairer_wanner_eq1_debug.rs new file mode 100644 index 00000000..1c5f4047 --- /dev/null +++ b/russell_ode/tests/test_radau5_hairer_wanner_eq1_debug.rs @@ -0,0 +1,47 @@ +use russell_lab::{approx_eq, format_fortran, Vector}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_radau5_hairer_wanner_eq1_debug() { + // get ODE system + let (system, mut data, mut args) = Samples::hairer_wanner_eq1(); + let ndim = system.get_ndim(); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-4; + params.debug = true; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], 9.068021382386648E-02, 1e-15); + approx_eq(stat.h_accepted, 1.272673814374611E+00, 1e-12); + + // compare with the analytical solution + let analytical = data.y_analytical.unwrap(); + let mut y1_correct = Vector::new(ndim); + analytical(&mut y1_correct, data.x1); + approx_eq(data.y0[0], y1_correct[0], 3e-5); + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}", format_fortran(data.y0[0])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 67); + assert_eq!(stat.n_jacobian, 1); + assert_eq!(stat.n_factor, 13); + assert_eq!(stat.n_lin_sol, 17); + assert_eq!(stat.n_steps, 15); + assert_eq!(stat.n_accepted, 15); + assert_eq!(stat.n_rejected, 0); + assert_eq!(stat.n_iterations, 1); + assert_eq!(stat.n_iterations_max, 2); +} diff --git a/russell_ode/tests/test_radau5_robertson.rs b/russell_ode/tests/test_radau5_robertson.rs new file mode 100644 index 00000000..c92f98af --- /dev/null +++ b/russell_ode/tests/test_radau5_robertson.rs @@ -0,0 +1,58 @@ +use russell_lab::{approx_eq, format_fortran}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_radau5_robertson() { + // get get ODE system + let (system, mut data, mut args) = Samples::robertson(); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-6; + params.set_tolerances(1e-8, 1e-2, None).unwrap(); + + // enable output of accepted steps + let mut out = Output::new(); + out.enable_step(&[0, 1, 2]); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], 9.886740138499884E-01, 1e-15); + approx_eq(data.y0[1], 3.447720471782070E-05, 1e-15); + approx_eq(data.y0[2], 1.129150894529390E-02, 1e-15); + approx_eq(stat.h_accepted, 8.160578540333708E-01, 1e-10); + + // print the results at accepted steps + let n_step = out.step_x.len(); + for i in 0..n_step { + println!( + "step ={:>4}, x ={:5.2}, y ={}{}{}", + i, + out.step_x[i], + format_fortran(out.step_y.get(&0).unwrap()[i]), + format_fortran(out.step_y.get(&1).unwrap()[i]), + format_fortran(out.step_y.get(&2).unwrap()[i]), + ); + } + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(data.y0[0]), format_fortran(data.y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 88); + assert_eq!(stat.n_jacobian, 8); + assert_eq!(stat.n_factor, 15); + assert_eq!(stat.n_lin_sol, 24); + assert_eq!(stat.n_steps, 17); + assert_eq!(stat.n_accepted, 15); + assert_eq!(stat.n_rejected, 1); + assert_eq!(stat.n_iterations_max, 2); +} diff --git a/russell_ode/tests/test_radau5_robertson_debug.rs b/russell_ode/tests/test_radau5_robertson_debug.rs new file mode 100644 index 00000000..8fd38350 --- /dev/null +++ b/russell_ode/tests/test_radau5_robertson_debug.rs @@ -0,0 +1,42 @@ +use russell_lab::{approx_eq, format_fortran}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_radau5_robertson_debug() { + // get get ODE system + let (system, mut data, mut args) = Samples::robertson(); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-6; + params.set_tolerances(1e-8, 1e-2, None).unwrap(); + params.debug = true; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], 9.886740138499884E-01, 1e-15); + approx_eq(data.y0[1], 3.447720471782070E-05, 1e-15); + approx_eq(data.y0[2], 1.129150894529390E-02, 1e-15); + approx_eq(stat.h_accepted, 8.160578540333708E-01, 1e-10); + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(data.y0[0]), format_fortran(data.y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 88); + assert_eq!(stat.n_jacobian, 8); + assert_eq!(stat.n_factor, 15); + assert_eq!(stat.n_lin_sol, 24); + assert_eq!(stat.n_steps, 17); + assert_eq!(stat.n_accepted, 15); + assert_eq!(stat.n_rejected, 1); + assert_eq!(stat.n_iterations_max, 2); +} diff --git a/russell_ode/tests/test_radau5_robertson_small_h.rs b/russell_ode/tests/test_radau5_robertson_small_h.rs new file mode 100644 index 00000000..59b6e972 --- /dev/null +++ b/russell_ode/tests/test_radau5_robertson_small_h.rs @@ -0,0 +1,38 @@ +use russell_lab::format_fortran; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_radau5_robertson_small_h() { + // get get ODE system + let (system, mut data, mut args) = Samples::robertson(); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-6; + params.debug = true; + + // this will cause h to become too small + params.set_tolerances(1e-2, 1e-2, None).unwrap(); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + let res = solver.solve(&mut data.y0, data.x0, data.x1, None, None, &mut args); + assert_eq!(res.err(), Some("the stepsize becomes too small")); + println!("ERROR: THE STEPSIZE BECOMES TOO SMALL"); + + // get statistics + let stat = solver.bench(); + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(data.y0[0]), format_fortran(data.y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 520); + assert_eq!(stat.n_jacobian, 57); + assert_eq!(stat.n_factor, 75); + assert_eq!(stat.n_lin_sol, 153); + assert_eq!(stat.n_steps, 75); + assert_eq!(stat.n_accepted, 60); + assert_eq!(stat.n_rejected, 4); + assert_eq!(stat.n_iterations_max, 4); +} diff --git a/russell_ode/tests/test_radau5_van_der_pol.rs b/russell_ode/tests/test_radau5_van_der_pol.rs new file mode 100644 index 00000000..cd70597c --- /dev/null +++ b/russell_ode/tests/test_radau5_van_der_pol.rs @@ -0,0 +1,57 @@ +use russell_lab::{approx_eq, format_fortran}; +use russell_ode::{Method, OdeSolver, Output, Params, Samples}; + +#[test] +fn test_radau5_van_der_pol() { + // get get ODE system + const EPS: f64 = 1e-6; + let (system, mut data, mut args) = Samples::van_der_pol(Some(EPS), false); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-6; + + // enable dense output with 0.2 spacing + let mut out = Output::new(); + out.enable_dense(0.2, &[0, 1]).unwrap(); + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, Some(&mut out), &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], 1.706163410178079E+00, 1e-14); + approx_eq(data.y0[1], -8.927971289301175E-01, 1e-12); + approx_eq(stat.h_accepted, 1.510987221365367E-01, 1.1e-8); + + // print dense output + let n_dense = out.dense_step_index.len(); + for i in 0..n_dense { + println!( + "step ={:>4}, x ={:5.2}, y ={}{}", + out.dense_step_index[i], + out.dense_x[i], + format_fortran(out.dense_y.get(&0).unwrap()[i]), + format_fortran(out.dense_y.get(&1).unwrap()[i]), + ); + } + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(data.y0[0]), format_fortran(data.y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 2248 + 1); // +1 because the fist step is a reject, thus initialize is called again + assert_eq!(stat.n_jacobian, 162); + assert_eq!(stat.n_factor, 253); + assert_eq!(stat.n_lin_sol, 668); + assert_eq!(stat.n_steps, 280); + assert_eq!(stat.n_accepted, 242); + assert_eq!(stat.n_rejected, 8); + assert_eq!(stat.n_iterations, 2); + assert_eq!(stat.n_iterations_max, 6); +} diff --git a/russell_ode/tests/test_radau5_van_der_pol_debug.rs b/russell_ode/tests/test_radau5_van_der_pol_debug.rs new file mode 100644 index 00000000..fd7058c4 --- /dev/null +++ b/russell_ode/tests/test_radau5_van_der_pol_debug.rs @@ -0,0 +1,42 @@ +use russell_lab::{approx_eq, format_fortran}; +use russell_ode::{Method, OdeSolver, Params, Samples}; + +#[test] +fn test_radau5_van_der_pol_debug() { + // get get ODE system + const EPS: f64 = 1e-6; + let (system, mut data, mut args) = Samples::van_der_pol(Some(EPS), false); + + // set configuration parameters + let mut params = Params::new(Method::Radau5); + params.step.h_ini = 1e-6; + params.debug = true; + + // solve the ODE system + let mut solver = OdeSolver::new(params, &system).unwrap(); + solver + .solve(&mut data.y0, data.x0, data.x1, None, None, &mut args) + .unwrap(); + + // get statistics + let stat = solver.bench(); + + // compare with radau5.f + approx_eq(data.y0[0], 1.706163410178079E+00, 1e-14); + approx_eq(data.y0[1], -8.927971289301175E-01, 1e-12); + approx_eq(stat.h_accepted, 1.510987221365367E-01, 1.1e-8); + + // print and check statistics + println!("{}", stat.summary()); + println!("y ={}{}", format_fortran(data.y0[0]), format_fortran(data.y0[1])); + println!("h ={}", format_fortran(stat.h_accepted)); + assert_eq!(stat.n_function, 2248 + 1); // +1 because the fist step is a reject, thus initialize is called again + assert_eq!(stat.n_jacobian, 162); + assert_eq!(stat.n_factor, 253); + assert_eq!(stat.n_lin_sol, 668); + assert_eq!(stat.n_steps, 280); + assert_eq!(stat.n_accepted, 242); + assert_eq!(stat.n_rejected, 8); + assert_eq!(stat.n_iterations, 2); + assert_eq!(stat.n_iterations_max, 6); +} diff --git a/russell_ode/zscripts/compare-test-results.bash b/russell_ode/zscripts/compare-test-results.bash new file mode 100755 index 00000000..3cb44420 --- /dev/null +++ b/russell_ode/zscripts/compare-test-results.bash @@ -0,0 +1,60 @@ +#!/bin/bash + +# the first argument is 0 or 1 to run with --features intel_mkl +INTEL_MKL=${1:-""} + +# the second argument is 0 or 1 to save the results to the data dir (and not compare them) +SAVE_DATA=${2:-""} + +TESTS=" +test_dopri5_arenstorf_debug \ +test_dopri5_arenstorf \ +test_dopri5_hairer_wanner_eq1 \ +test_dopri5_van_der_pol_debug \ +test_dopri8_van_der_pol_debug \ +test_dopri8_van_der_pol \ +test_radau5_amplifier1t \ +test_radau5_hairer_wanner_eq1_debug \ +test_radau5_hairer_wanner_eq1 \ +test_radau5_robertson_debug \ +test_radau5_robertson_small_h \ +test_radau5_robertson \ +test_radau5_van_der_pol_debug \ +test_radau5_van_der_pol \ +" + +sum="" + +for test in $TESTS; do + temporary="/tmp/${test/test_/russell_}.txt" + correct="data/${test/test_/russell_}.txt" + + if [ "${SAVE_DATA}" = "1" ]; then + temporary=$correct + fi + + # run the test + if [ "${INTEL_MKL}" = "1" ]; then + echo "running with: --features russell_lab/intel_mkl" + cargo test --test $test --features russell_lab/intel_mkl -- --nocapture --quiet > $temporary + else + cargo test --test $test -- --nocapture --quiet > $temporary + fi + + # delete the last two lines (blank line, the cargo output, and a dot) + sed -i '$d' $temporary + sed -i '$d' $temporary + sed -i '$d' $temporary + + # check the difference + if [ ! "${SAVE_DATA}" = "1" ]; then + res=`diff -u $temporary $correct` + sum="${sum}${res}" + fi +done + +if [ "${SAVE_DATA}" = "1" ]; then + echo "results saved to the data dir" +else + echo "this should be empty: [${sum}]" +fi diff --git a/russell_sparse/Cargo.toml b/russell_sparse/Cargo.toml index 4fc7d033..376f3e03 100644 --- a/russell_sparse/Cargo.toml +++ b/russell_sparse/Cargo.toml @@ -16,10 +16,15 @@ local_libs = [] intel_mkl = ["local_libs"] [dependencies] +num-complex = { version = "0.4", features = ["serde"] } +num-traits = "0.2" russell_lab = { path = "../russell_lab", version = "0.8.0" } structopt = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +[dev-dependencies] +serial_test = "3.0" + [build-dependencies] cc = "1.0" diff --git a/russell_sparse/README.md b/russell_sparse/README.md index c563b1f5..0b6ecf5e 100644 --- a/russell_sparse/README.md +++ b/russell_sparse/README.md @@ -75,7 +75,7 @@ fn main() -> Result<(), StrError> { let mut umfpack = SolverUMFPACK::new()?; // allocate the coefficient matrix - let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; coo.put(0, 0, 0.2)?; coo.put(0, 1, 0.2)?; coo.put(1, 0, 0.5)?; @@ -174,7 +174,8 @@ The output looks like this: "umfpack_rcond_estimate": 0.0 }, "determinant": { - "mantissa": 0.0, + "mantissa_real": 0.0, + "mantissa_imag": 0.0, "base": 2.0, "exponent": 0.0 }, diff --git a/russell_sparse/build.rs b/russell_sparse/build.rs index a09f77b6..7b69bbe7 100644 --- a/russell_sparse/build.rs +++ b/russell_sparse/build.rs @@ -5,14 +5,17 @@ const MKL_VERSION: &str = "2023.2.0"; fn handle_local_libs() { // local MUMPS cc::Build::new() + .file("c_code/interface_complex_mumps.c") .file("c_code/interface_mumps.c") .include("/usr/local/include/mumps") .compile("c_code_interface_mumps"); println!("cargo:rustc-link-search=native=/usr/local/lib/mumps"); println!("cargo:rustc-link-lib=dylib=dmumps_cpmech"); + println!("cargo:rustc-link-lib=dylib=zmumps_cpmech"); println!("cargo:rustc-cfg=local_mumps"); // local UMFPACK cc::Build::new() + .file("c_code/interface_complex_umfpack.c") .file("c_code/interface_umfpack.c") .include("/usr/local/include/umfpack") .compile("c_code_interface_umfpack"); @@ -25,11 +28,14 @@ fn handle_local_libs() { fn handle_local_libs() { // MUMPS cc::Build::new() + .file("c_code/interface_complex_mumps.c") .file("c_code/interface_mumps.c") .compile("c_code_interface_mumps"); println!("cargo:rustc-link-lib=dylib=dmumps_seq"); + println!("cargo:rustc-link-lib=dylib=zmumps_seq"); // UMFPACK cc::Build::new() + .file("c_code/interface_complex_umfpack.c") .file("c_code/interface_umfpack.c") .include("/usr/include/suitesparse") .compile("c_code_interface_umfpack"); diff --git a/russell_sparse/c_code/constants.h b/russell_sparse/c_code/constants.h index 0c32bb20..3c663193 100644 --- a/russell_sparse/c_code/constants.h +++ b/russell_sparse/c_code/constants.h @@ -14,3 +14,26 @@ #define C_FALSE 0 #define C_BOOL int32_t + +#define COMPLEX64 double + +// UMFPACK ------------------------------------------------------------------------------------------- + +#define UMFPACK_PRINT_LEVEL_SILENT 0.0 // page 116 +#define UMFPACK_PRINT_LEVEL_VERBOSE 2.0 // page 116 + +// MUMPS --------------------------------------------------------------------------------------------- + +#define MUMPS_IGNORED 0 // to ignore the Fortran communicator since we're not using MPI + +#define MUMPS_JOB_INITIALIZE -1 // section 5.1.1, page 24 +#define MUMPS_JOB_TERMINATE -2 // section 5.1.1, page 24 +#define MUMPS_JOB_ANALYZE 1 // section 5.1.1, page 24 +#define MUMPS_JOB_FACTORIZE 2 // section 5.1.1, page 25 +#define MUMPS_JOB_SOLVE 3 // section 5.1.1, page 25 + +#define MUMPS_PAR_HOST_ALSO_WORKS 1 // section 5.1.4, page 26 +#define MUMPS_ICNTL5_ASSEMBLED_MATRIX 0 // section 5.2.2, page 27 +#define MUMPS_ICNTL18_CENTRALIZED 0 // section 5.2.2, page 27 +#define MUMPS_ICNTL6_PERMUT_AUTO 7 // section 5.3, page 32 +#define MUMPS_ICNTL28_SEQUENTIAL 1 // section 5.4, page 33 diff --git a/russell_sparse/c_code/interface_complex_mumps.c b/russell_sparse/c_code/interface_complex_mumps.c new file mode 100644 index 00000000..c58230f9 --- /dev/null +++ b/russell_sparse/c_code/interface_complex_mumps.c @@ -0,0 +1,264 @@ +#include +#include +#include +#include + +#include "zmumps_c.h" + +#include "constants.h" + +#define ICNTL(i) icntl[(i)-1] // macro to make indices match documentation +#define RINFOG(i) rinfog[(i)-1] // macro to make indices match documentation +#define INFOG(i) infog[(i)-1] // macro to make indices match documentation +#define INFO(i) info[(i)-1] // macro to make indices match documentation + +/// @brief Holds the data for MUMPS +struct InterfaceComplexMUMPS { + /// @brief Holds the MUMPS data structure + ZMUMPS_STRUC_C data; + + /// @brief job init completed successfully + int32_t done_job_init; + + /// @brief indicates that the initialization has been completed + C_BOOL initialization_completed; + + /// @brief indicates that the factorization (at least once) has been completed + C_BOOL factorization_completed; +}; + +/// @brief Sets verbose mode +/// @param data Is the MUMPS data structure +/// @param verbose Shows messages or not +static inline void set_mumps_verbose(ZMUMPS_STRUC_C *data, int32_t verbose) { + if (verbose == C_TRUE) { + data->ICNTL(1) = 6; // standard output stream + data->ICNTL(2) = 0; // output stream + data->ICNTL(3) = 6; // standard output stream + data->ICNTL(4) = 3; // errors, warnings, and main statistics printed + } else { + data->ICNTL(1) = -1; // no output messages + data->ICNTL(2) = -1; // no warnings + data->ICNTL(3) = -1; // no global information + data->ICNTL(4) = -1; // message level + } +} + +/// @brief Allocates a new MUMPS interface +struct InterfaceComplexMUMPS *complex_solver_mumps_new() { + struct InterfaceComplexMUMPS *solver = (struct InterfaceComplexMUMPS *)malloc(sizeof(struct InterfaceComplexMUMPS)); + + if (solver == NULL) { + return NULL; + } + + solver->data.irn = NULL; + solver->data.jcn = NULL; + solver->data.a = NULL; + solver->done_job_init = C_FALSE; + solver->initialization_completed = C_FALSE; + solver->factorization_completed = C_FALSE; + + return solver; +} + +/// @brief Deallocates the MUMPS interface +void complex_solver_mumps_drop(struct InterfaceComplexMUMPS *solver) { + if (solver == NULL) { + return; + } + + // prevent MUMPS freeing these + solver->data.irn = NULL; + solver->data.jcn = NULL; + solver->data.a = NULL; + + if (solver->done_job_init == C_TRUE) { + set_mumps_verbose(&solver->data, C_FALSE); + solver->data.job = MUMPS_JOB_TERMINATE; + zmumps_c(&solver->data); + } + + free(solver); +} + +/// @brief Perform analysis just once (considering that the matrix structure remains constant) +/// @return A success or fail code +int32_t complex_solver_mumps_initialize(struct InterfaceComplexMUMPS *solver, + int32_t ordering, + int32_t scaling, + int32_t pct_inc_workspace, + int32_t max_work_memory, + int32_t openmp_num_threads, + C_BOOL verbose, + C_BOOL general_symmetric, + C_BOOL positive_definite, + int32_t ndim, + int32_t nnz, + int32_t const *indices_i, + int32_t const *indices_j, + ZMUMPS_COMPLEX const *values_aij) { + if (solver == NULL) { + return ERROR_NULL_POINTER; + } + + if (solver->initialization_completed == C_TRUE) { + return ERROR_ALREADY_INITIALIZED; + } + + solver->data.comm_fortran = MUMPS_IGNORED; + solver->data.par = MUMPS_PAR_HOST_ALSO_WORKS; + solver->data.sym = 0; // unsymmetric (page 27) + if (general_symmetric == C_TRUE) { + solver->data.sym = 2; // general symmetric (page 27) + } else if (positive_definite == C_TRUE) { + solver->data.sym = 1; // symmetric positive-definite (page 27) + } + + set_mumps_verbose(&solver->data, C_FALSE); + solver->data.job = MUMPS_JOB_INITIALIZE; + zmumps_c(&solver->data); + if (solver->data.INFOG(1) != 0) { + return solver->data.INFOG(1); + } + + solver->done_job_init = C_TRUE; + + if (strcmp(solver->data.version_number, MUMPS_VERSION) != 0) { + printf("\n\n\nERROR: MUMPS LIBRARY VERSION = "); + int i; + for (i = 0; i < MUMPS_VERSION_MAX_LEN; i++) { + printf("%c", solver->data.version_number[i]); + } + printf(" != INCLUDE VERSION = %s \n\n\n", MUMPS_VERSION); + return ERROR_VERSION; + } + + solver->data.ICNTL(5) = MUMPS_ICNTL5_ASSEMBLED_MATRIX; + solver->data.ICNTL(6) = MUMPS_ICNTL6_PERMUT_AUTO; + solver->data.ICNTL(7) = ordering; + solver->data.ICNTL(8) = scaling; + solver->data.ICNTL(14) = pct_inc_workspace; + solver->data.ICNTL(16) = openmp_num_threads; + solver->data.ICNTL(18) = MUMPS_ICNTL18_CENTRALIZED; + solver->data.ICNTL(23) = max_work_memory; + solver->data.ICNTL(28) = MUMPS_ICNTL28_SEQUENTIAL; + solver->data.ICNTL(29) = MUMPS_IGNORED; + + solver->data.n = ndim; + solver->data.nz = nnz; + solver->data.irn = (int *)indices_i; + solver->data.jcn = (int *)indices_j; + solver->data.a = (ZMUMPS_COMPLEX *)values_aij; + + set_mumps_verbose(&solver->data, verbose); + solver->data.job = MUMPS_JOB_ANALYZE; + zmumps_c(&solver->data); + + if (solver->data.INFO(1) != 0) { + return solver->data.INFOG(1); // error + } + + solver->initialization_completed = C_TRUE; + + return SUCCESSFUL_EXIT; +} + +/// @brief Performs the factorization +/// @return A success or fail code +int32_t complex_solver_mumps_factorize(struct InterfaceComplexMUMPS *solver, + int32_t *effective_ordering, + int32_t *effective_scaling, + double *determinant_coefficient_real, + double *determinant_coefficient_imag, + double *determinant_exponent, + C_BOOL compute_determinant, + C_BOOL verbose) { + if (solver == NULL) { + return ERROR_NULL_POINTER; + } + + if (solver->initialization_completed == C_FALSE) { + return ERROR_NEED_INITIALIZATION; + } + + // handle requests + + if (compute_determinant == C_TRUE) { + solver->data.ICNTL(33) = 1; + solver->data.ICNTL(8) = 0; // it's recommended to disable scaling when computing the determinant + } else { + solver->data.ICNTL(33) = 0; + } + + // perform factorization + + set_mumps_verbose(&solver->data, verbose); + solver->data.job = MUMPS_JOB_FACTORIZE; + zmumps_c(&solver->data); + + // save the output params + + *effective_ordering = solver->data.INFOG(7); + *effective_scaling = solver->data.INFOG(33); + + // read the determinant + + if (compute_determinant == C_TRUE && solver->data.ICNTL(33) == 1) { + *determinant_coefficient_real = solver->data.RINFOG(12); + *determinant_coefficient_imag = solver->data.RINFOG(13); + *determinant_exponent = solver->data.INFOG(34); + } else { + *determinant_coefficient_real = 0.0; + *determinant_coefficient_imag = 0.0; + *determinant_exponent = 0.0; + } + + solver->factorization_completed = C_TRUE; + + return solver->data.INFOG(1); +} + +/// @brief Computes the solution of the linear system +/// @param solver Is a pointer to the solver interface +/// @param rhs Is the right-hand side on the input and the vector of unknow values x on the output +/// @param error_analysis_array_len_8 array of size 8 to hold the results from the error analysis +/// @param error_analysis_option ICNTL(11): 0 (nothing), 1 (all; slow), 2 (just errors) +/// @param verbose Shows messages +/// @return A success or fail code +int32_t complex_solver_mumps_solve(struct InterfaceComplexMUMPS *solver, + ZMUMPS_COMPLEX *rhs, + double *error_analysis_array_len_8, + int32_t error_analysis_option, + C_BOOL verbose) { + if (solver == NULL) { + return ERROR_NULL_POINTER; + } + + if (solver->factorization_completed == C_FALSE) { + return ERROR_NEED_FACTORIZATION; + } + + solver->data.ICNTL(11) = error_analysis_option; + + solver->data.rhs = rhs; + + set_mumps_verbose(&solver->data, verbose); + solver->data.job = MUMPS_JOB_SOLVE; + zmumps_c(&solver->data); + + if (error_analysis_option > 0) { + error_analysis_array_len_8[0] = solver->data.RINFOG(4); // norm_a + error_analysis_array_len_8[1] = solver->data.RINFOG(5); // norm_x + error_analysis_array_len_8[2] = solver->data.RINFOG(6); // resid + error_analysis_array_len_8[3] = solver->data.RINFOG(7); // omega1 + error_analysis_array_len_8[4] = solver->data.RINFOG(8); // omega2 + if (error_analysis_option == 1) { + error_analysis_array_len_8[5] = solver->data.RINFOG(9); // delta + error_analysis_array_len_8[6] = solver->data.RINFOG(10); // cond1 + error_analysis_array_len_8[7] = solver->data.RINFOG(11); // cond2 + } + } + + return solver->data.INFOG(1); +} diff --git a/russell_sparse/c_code/interface_complex_umfpack.c b/russell_sparse/c_code/interface_complex_umfpack.c new file mode 100644 index 00000000..6f7ef634 --- /dev/null +++ b/russell_sparse/c_code/interface_complex_umfpack.c @@ -0,0 +1,232 @@ +#include +#include + +#include "umfpack.h" + +#include "constants.h" + +/// @brief Holds the data for UMFPACK +struct InterfaceComplexUMFPACK { + /// @brief Holds control flags + double control[UMFPACK_CONTROL]; + + /// @brief Holds information data + double info[UMFPACK_INFO]; + + /// @brief Is a handle to symbolic factorization results + void *symbolic; + + /// @brief Is a handle to numeric factorization results + void *numeric; + + /// @brief indicates that the initialization has been completed + C_BOOL initialization_completed; + + /// @brief Indicates that the factorization (at least once) has been completed + C_BOOL factorization_completed; +}; + +/// @brief Sets verbose mode +/// @param solver Is a pointer to the solver interface +/// @param verbose Shows messages or not +static inline void set_complex_umfpack_verbose(struct InterfaceComplexUMFPACK *solver, int32_t verbose) { + if (verbose == C_TRUE) { + solver->control[UMFPACK_PRL] = UMFPACK_PRINT_LEVEL_VERBOSE; + } else { + solver->control[UMFPACK_PRL] = UMFPACK_PRINT_LEVEL_SILENT; + } +} + +/// @brief Allocates a new UMFPACK interface +struct InterfaceComplexUMFPACK *complex_solver_umfpack_new() { + struct InterfaceComplexUMFPACK *solver = (struct InterfaceComplexUMFPACK *)malloc(sizeof(struct InterfaceComplexUMFPACK)); + + if (solver == NULL) { + return NULL; + } + + umfpack_zi_defaults(solver->control); + + solver->symbolic = NULL; + solver->numeric = NULL; + solver->initialization_completed = C_FALSE; + solver->factorization_completed = C_FALSE; + + return solver; +} + +/// @brief Deallocates the UMFPACK interface +void complex_solver_umfpack_drop(struct InterfaceComplexUMFPACK *solver) { + if (solver == NULL) { + return; + } + + if (solver->symbolic != NULL) { + umfpack_zi_free_symbolic(&solver->symbolic); + free(solver->symbolic); + } + if (solver->numeric != NULL) { + umfpack_zi_free_numeric(&solver->numeric); + free(solver->numeric); + } + + free(solver); +} + +/// @brief Performs the symbolic factorization +/// @return A success or fail code +int32_t complex_solver_umfpack_initialize(struct InterfaceComplexUMFPACK *solver, + int32_t ordering, + int32_t scaling, + C_BOOL verbose, + C_BOOL enforce_unsymmetric_strategy, + int32_t ndim, + const int32_t *col_pointers, + const int32_t *row_indices, + const COMPLEX64 *values) { + if (solver == NULL) { + return ERROR_NULL_POINTER; + } + + if (solver->initialization_completed == C_TRUE) { + return ERROR_ALREADY_INITIALIZED; + } + + solver->control[UMFPACK_STRATEGY] = UMFPACK_STRATEGY_AUTO; + if (enforce_unsymmetric_strategy == C_TRUE) { + solver->control[UMFPACK_STRATEGY] = UMFPACK_STRATEGY_UNSYMMETRIC; + } + + solver->control[UMFPACK_ORDERING] = ordering; + solver->control[UMFPACK_SCALE] = scaling; + + set_complex_umfpack_verbose(solver, verbose); + + int code = umfpack_zi_symbolic(ndim, + ndim, + col_pointers, + row_indices, + values, + NULL, + &solver->symbolic, + solver->control, + solver->info); + if (code != UMFPACK_OK) { + return code; + } + + solver->initialization_completed = C_TRUE; + + return SUCCESSFUL_EXIT; +} + +/// @brief Performs the numeric factorization +int32_t complex_solver_umfpack_factorize(struct InterfaceComplexUMFPACK *solver, + int32_t *effective_strategy, + int32_t *effective_ordering, + int32_t *effective_scaling, + double *rcond_estimate, + double *determinant_coefficient_real, + double *determinant_coefficient_imag, + double *determinant_exponent, + C_BOOL compute_determinant, + C_BOOL verbose, + const int32_t *col_pointers, + const int32_t *row_indices, + const COMPLEX64 *values) { + if (solver == NULL) { + return ERROR_NULL_POINTER; + } + + if (solver->initialization_completed == C_FALSE) { + return ERROR_NEED_INITIALIZATION; + } + + if (solver->factorization_completed == C_TRUE) { + // free the previous numeric to avoid memory leak + umfpack_zi_free_numeric(&solver->numeric); + } + + // perform numeric factorization + int code = umfpack_zi_numeric(col_pointers, + row_indices, + values, + NULL, + solver->symbolic, + &solver->numeric, + solver->control, + solver->info); + if (verbose == C_TRUE) { + umfpack_zi_report_info(solver->control, solver->info); + } + + // save strategy, ordering, and scaling + *effective_strategy = solver->info[UMFPACK_STRATEGY_USED]; + *effective_ordering = solver->info[UMFPACK_ORDERING_USED]; + *effective_scaling = solver->control[UMFPACK_SCALE]; + + // reciprocal condition number estimate + *rcond_estimate = solver->info[UMFPACK_RCOND]; + + // compute determinant + if (compute_determinant == C_TRUE) { + code = umfpack_zi_get_determinant(determinant_coefficient_real, + determinant_coefficient_imag, + determinant_exponent, + solver->numeric, + solver->info); + } else { + *determinant_coefficient_real = 0.0; + *determinant_coefficient_imag = 0.0; + *determinant_exponent = 0.0; + } + + solver->factorization_completed = C_TRUE; + + return code; +} + +/// @brief Computes the solution of the linear system +/// @param solver Is a pointer to the solver interface +/// @param x Is the left-hand side vector (unknowns) +/// @param rhs Is the right-hand side vector +/// @param col_pointers The column pointers array with size = ncol + 1 +/// @param row_indices The row indices array with size = nnz (number of non-zeros) +/// @param values The values array with size = nnz (number of non-zeros) +/// @param verbose Shows messages +/// @return A success or fail code +int32_t complex_solver_umfpack_solve(struct InterfaceComplexUMFPACK *solver, + COMPLEX64 *x, + const COMPLEX64 *rhs, + const int32_t *col_pointers, + const int32_t *row_indices, + const COMPLEX64 *values, + C_BOOL verbose) { + if (solver == NULL) { + return ERROR_NULL_POINTER; + } + + if (solver->factorization_completed == C_FALSE) { + return ERROR_NEED_FACTORIZATION; + } + + set_complex_umfpack_verbose(solver, verbose); + + int code = umfpack_zi_solve(UMFPACK_A, + col_pointers, + row_indices, + values, + NULL, + x, + NULL, + rhs, + NULL, + solver->numeric, + solver->control, + solver->info); + if (verbose == C_TRUE) { + umfpack_zi_report_info(solver->control, solver->info); + } + + return code; +} diff --git a/russell_sparse/c_code/interface_intel_dss.c b/russell_sparse/c_code/interface_intel_dss.c index 10a31d81..f77e6348 100644 --- a/russell_sparse/c_code/interface_intel_dss.c +++ b/russell_sparse/c_code/interface_intel_dss.c @@ -180,6 +180,9 @@ int32_t solver_intel_dss_factorize(struct InterfaceIntelDSS *solver, } *determinant_exponent = stat_out[0]; *determinant_coefficient = stat_out[1]; + } else { + *determinant_coefficient = 0.0; + *determinant_exponent = 0.0; } solver->factorization_completed = C_TRUE; diff --git a/russell_sparse/c_code/interface_mumps.c b/russell_sparse/c_code/interface_mumps.c index dcf83ace..fe4bb920 100644 --- a/russell_sparse/c_code/interface_mumps.c +++ b/russell_sparse/c_code/interface_mumps.c @@ -12,20 +12,6 @@ #define INFOG(i) infog[(i)-1] // macro to make indices match documentation #define INFO(i) info[(i)-1] // macro to make indices match documentation -const MUMPS_INT MUMPS_IGNORED = 0; // to ignore the Fortran communicator since we're not using MPI - -const MUMPS_INT MUMPS_JOB_INITIALIZE = -1; // section 5.1.1, page 24 -const MUMPS_INT MUMPS_JOB_TERMINATE = -2; // section 5.1.1, page 24 -const MUMPS_INT MUMPS_JOB_ANALYZE = 1; // section 5.1.1, page 24 -const MUMPS_INT MUMPS_JOB_FACTORIZE = 2; // section 5.1.1, page 25 -const MUMPS_INT MUMPS_JOB_SOLVE = 3; // section 5.1.1, page 25 - -const MUMPS_INT MUMPS_PAR_HOST_ALSO_WORKS = 1; // section 5.1.4, page 26 -const MUMPS_INT MUMPS_ICNTL5_ASSEMBLED_MATRIX = 0; // section 5.2.2, page 27 -const MUMPS_INT MUMPS_ICNTL18_CENTRALIZED = 0; // section 5.2.2, page 27 -const MUMPS_INT MUMPS_ICNTL6_PERMUT_AUTO = 7; // section 5.3, page 32 -const MUMPS_INT MUMPS_ICNTL28_SEQUENTIAL = 1; // section 5.4, page 33 - /// @brief Holds the data for MUMPS struct InterfaceMUMPS { /// @brief Holds the MUMPS data structure diff --git a/russell_sparse/c_code/interface_umfpack.c b/russell_sparse/c_code/interface_umfpack.c index 0c8b5104..301f42f5 100644 --- a/russell_sparse/c_code/interface_umfpack.c +++ b/russell_sparse/c_code/interface_umfpack.c @@ -5,9 +5,6 @@ #include "constants.h" -const double UMFPACK_PRINT_LEVEL_SILENT = 0.0; // page 116 -const double UMFPACK_PRINT_LEVEL_VERBOSE = 2.0; // page 116 - /// @brief Holds the data for UMFPACK struct InterfaceUMFPACK { /// @brief Holds control flags @@ -174,6 +171,9 @@ int32_t solver_umfpack_factorize(struct InterfaceUMFPACK *solver, determinant_exponent, solver->numeric, solver->info); + } else { + *determinant_coefficient = 0.0; + *determinant_exponent = 0.0; } solver->factorization_completed = C_TRUE; @@ -222,26 +222,3 @@ int32_t solver_umfpack_solve(struct InterfaceUMFPACK *solver, return code; } - -/// @brief Converts COO matrix (with possible duplicates) to CSC matrix -/// @param nrow Is the number of rows -/// @param ncol Is the number of columns -/// @param nnz Is the number of non-zero values, including duplicates -/// @param indices_i Are the CooMatrix row indices -/// @param indices_j Are the CooMatrix column indices -/// @param values_aij Are the CooMatrix values, including duplicates -/// @return A success or fail code -int32_t umfpack_coo_to_csc(int32_t *col_pointers, - int32_t *row_indices, - double *values, - int32_t nrow, - int32_t ncol, - int32_t nnz, - int32_t const *indices_i, - int32_t const *indices_j, - double const *values_aij) { - int code = umfpack_di_triplet_to_col(nrow, ncol, nnz, - indices_i, indices_j, values_aij, - col_pointers, row_indices, values, NULL); - return code; -} diff --git a/russell_sparse/data/mumps-pre2.json b/russell_sparse/data/mumps-pre2.json index 8cacf5dc..f5c827de 100644 --- a/russell_sparse/data/mumps-pre2.json +++ b/russell_sparse/data/mumps-pre2.json @@ -25,7 +25,8 @@ "umfpack_rcond_estimate": 0.0 }, "determinant": { - "mantissa": 0.0, + "mantissa_real": 0.0, + "mantissa_imag": 0.0, "base": 2.0, "exponent": 0.0 }, diff --git a/russell_sparse/examples/complex_system.rs b/russell_sparse/examples/complex_system.rs new file mode 100644 index 00000000..79385a0f --- /dev/null +++ b/russell_sparse/examples/complex_system.rs @@ -0,0 +1,116 @@ +use num_complex::Complex64; +use russell_lab::{complex_vec_approx_eq, cpx, ComplexVector}; +use russell_sparse::prelude::*; +use russell_sparse::StrError; + +fn solve(genie: Genie) -> Result<(), StrError> { + // Given the matrix of complex numbers: + // + // ┌ ┐ + // │ 19.73 12.11-i 5i 0 0 │ + // │ -0.51i 32.3+7i 23.07 i 0 │ + // A = │ 0 -0.51i 70+7.3i 3.95 19+31.83i │ + // │ 0 0 1+1.1i 50.17 45.51 │ + // │ 0 0 0 -9.351i 55 │ + // └ ┘ + // + // and the vector: + // + // ┌ ┐ + // │ 77.38+8.82i │ + // │ 157.48+19.8i │ + // b = │ 1175.62+20.69i │ + // │ 912.12-801.75i │ + // │ 550-1060.4i │ + // └ ┘ + // + // find x such that: + // + // A.x = b + // + // The solution is approximately: + // + // ┌ ┐ + // │ 3.3-i │ + // │ 1+0.17i │ + // x = │ 5.5 │ + // │ 9 │ + // │ 10-17.75i │ + // └ ┘ + + // constants + let ndim = 5; // number of rows = number of columns + let nnz = 16; // number of non-zero values, including duplicates + + // input matrix in Complex Triplet format + let mut coo = ComplexSparseMatrix::new_coo(ndim, ndim, nnz, None)?; + + // first column + coo.put(0, 0, cpx!(19.73, 0.00))?; + coo.put(1, 0, cpx!(0.00, -0.51))?; + + // second column + coo.put(0, 1, cpx!(12.11, -1.00))?; + coo.put(1, 1, cpx!(32.30, 7.00))?; + coo.put(2, 1, cpx!(0.00, -0.51))?; + + // third column + coo.put(0, 2, cpx!(0.00, 5.0))?; + coo.put(1, 2, cpx!(23.07, 0.0))?; + coo.put(2, 2, cpx!(70.00, 7.3))?; + coo.put(3, 2, cpx!(1.00, 1.1))?; + + // fourth column + coo.put(1, 3, cpx!(0.00, 1.000))?; + coo.put(2, 3, cpx!(3.95, 0.000))?; + coo.put(3, 3, cpx!(50.17, 0.000))?; + coo.put(4, 3, cpx!(0.00, -9.351))?; + + // fifth column + coo.put(2, 4, cpx!(19.00, 31.83))?; + coo.put(3, 4, cpx!(45.51, 0.00))?; + coo.put(4, 4, cpx!(55.00, 0.00))?; + + // right-hand-side + let b = ComplexVector::from(&[ + cpx!(77.38, 8.82), + cpx!(157.48, 19.8), + cpx!(1175.62, 20.69), + cpx!(912.12, -801.75), + cpx!(550.00, -1060.4), + ]); + + // allocate solver + let mut solver = ComplexLinSolver::new(genie)?; + + // parameters + let mut params = LinSolParams::new(); + params.verbose = false; + + // call factorize and solve + let mut x = ComplexVector::new(ndim); + solver.actual.factorize(&mut coo, Some(params))?; + solver.actual.solve(&mut x, &coo, &b, false)?; + println!("x =\n{}", x); + + // check + let x_correct = &[ + cpx!(3.3, -1.00), + cpx!(1.0, 0.17), + cpx!(5.5, 0.00), + cpx!(9.0, 0.00), + cpx!(10.0, -17.75), + ]; + complex_vec_approx_eq(x.as_data(), x_correct, 1e-3); + Ok(()) +} + +fn main() -> Result<(), StrError> { + println!("MUMPS ====================================="); + solve(Genie::Mumps)?; + + println!("\nUMFPACK ====================================="); + solve(Genie::Umfpack)?; + + Ok(()) +} diff --git a/russell_sparse/examples/doc_coo_from_arrays.rs b/russell_sparse/examples/doc_coo_from_arrays.rs index 16c4fd2a..7a468207 100644 --- a/russell_sparse/examples/doc_coo_from_arrays.rs +++ b/russell_sparse/examples/doc_coo_from_arrays.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), StrError> { 1.0, /*dup*/ 1.0, 3.0, 3.0, -1.0, 4.0, 4.0, -3.0, 1.0, 2.0, 2.0, 6.0, 1.0, ]; let symmetry = None; - let coo = CooMatrix::from(nrow, ncol, row_indices, col_indices, values, symmetry, false)?; + let coo = CooMatrix::from(nrow, ncol, row_indices, col_indices, values, symmetry)?; // covert to dense let a = coo.as_dense(); diff --git a/russell_sparse/examples/doc_coo_new_put_reset.rs b/russell_sparse/examples/doc_coo_new_put_reset.rs index 9bc10d5f..272582bf 100644 --- a/russell_sparse/examples/doc_coo_new_put_reset.rs +++ b/russell_sparse/examples/doc_coo_new_put_reset.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), StrError> { // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - let mut coo = CooMatrix::new(5, 5, 13, None, true)?; + let mut coo = CooMatrix::new(5, 5, 13, None)?; coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(1, 0, 3.0)?; diff --git a/russell_sparse/examples/doc_csc_from_coo.rs b/russell_sparse/examples/doc_csc_from_coo.rs index 5817741e..6911072c 100644 --- a/russell_sparse/examples/doc_csc_from_coo.rs +++ b/russell_sparse/examples/doc_csc_from_coo.rs @@ -9,7 +9,7 @@ fn main() -> Result<(), StrError> { // . . 1 . . // . 4 2 . 1 let (nrow, ncol, nnz) = (5, 5, 13); - let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false)?; + let mut coo = CooMatrix::new(nrow, ncol, nnz, None)?; coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(1, 0, 3.0)?; diff --git a/russell_sparse/examples/doc_csr_from_coo.rs b/russell_sparse/examples/doc_csr_from_coo.rs index bb36c195..deaac306 100644 --- a/russell_sparse/examples/doc_csr_from_coo.rs +++ b/russell_sparse/examples/doc_csr_from_coo.rs @@ -9,7 +9,7 @@ fn main() -> Result<(), StrError> { // . . 1 . . // . 4 2 . 1 let (nrow, ncol, nnz) = (5, 5, 13); - let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false)?; + let mut coo = CooMatrix::new(nrow, ncol, nnz, None)?; coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(1, 0, 3.0)?; diff --git a/russell_sparse/examples/doc_lin_solver_compute.rs b/russell_sparse/examples/doc_lin_solver_compute.rs index 8c95a79c..0e558a9a 100644 --- a/russell_sparse/examples/doc_lin_solver_compute.rs +++ b/russell_sparse/examples/doc_lin_solver_compute.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), StrError> { let nnz = 5; // number of non-zero values // allocate the coefficient matrix - let mut mat = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + let mut mat = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; mat.put(0, 0, 0.2)?; mat.put(0, 1, 0.2)?; mat.put(1, 0, 0.5)?; diff --git a/russell_sparse/examples/doc_lin_solver_umfpack_tiny.rs b/russell_sparse/examples/doc_lin_solver_umfpack_tiny.rs index 03d65c5f..e9ecbdc8 100644 --- a/russell_sparse/examples/doc_lin_solver_umfpack_tiny.rs +++ b/russell_sparse/examples/doc_lin_solver_umfpack_tiny.rs @@ -11,7 +11,7 @@ fn main() -> Result<(), StrError> { let mut solver = LinSolver::new(Genie::Umfpack)?; // allocate the coefficient matrix - let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; coo.put(0, 0, 0.2)?; coo.put(0, 1, 0.2)?; coo.put(1, 0, 0.5)?; diff --git a/russell_sparse/examples/doc_umfpack_quickstart_coo.rs b/russell_sparse/examples/doc_umfpack_quickstart_coo.rs index 29b5054f..d262b5f4 100644 --- a/russell_sparse/examples/doc_umfpack_quickstart_coo.rs +++ b/russell_sparse/examples/doc_umfpack_quickstart_coo.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), StrError> { // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(1, 0, 3.0)?; @@ -54,7 +54,7 @@ fn main() -> Result<(), StrError> { // analysis let mut stats = StatsLinSol::new(); umfpack.update_stats(&mut stats); - let (mx, ex) = (stats.determinant.mantissa, stats.determinant.exponent); + let (mx, ex) = (stats.determinant.mantissa_real, stats.determinant.exponent); println!("det(a) = {:?}", mx * f64::powf(10.0, ex)); println!("rcond = {:?}", stats.output.umfpack_rcond_estimate); Ok(()) diff --git a/russell_sparse/examples/doc_umfpack_tiny.rs b/russell_sparse/examples/doc_umfpack_tiny.rs index 6b97bf46..9271822e 100644 --- a/russell_sparse/examples/doc_umfpack_tiny.rs +++ b/russell_sparse/examples/doc_umfpack_tiny.rs @@ -11,7 +11,7 @@ fn main() -> Result<(), StrError> { let mut umfpack = SolverUMFPACK::new()?; // allocate the coefficient matrix - let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; coo.put(0, 0, 0.2)?; coo.put(0, 1, 0.2)?; coo.put(1, 0, 0.5)?; diff --git a/russell_sparse/examples/mumps_solve_small.rs b/russell_sparse/examples/mumps_solve_small.rs index d08f3ffc..90635492 100644 --- a/russell_sparse/examples/mumps_solve_small.rs +++ b/russell_sparse/examples/mumps_solve_small.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), StrError> { // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, true)?; + let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate coo.put(1, 0, 3.0)?; @@ -60,7 +60,7 @@ fn main() -> Result<(), StrError> { let norm_ai = mat_norm(&ai, Norm::Inf); let cond = norm_a * norm_ai; let rcond = 1.0 / cond; - let verify = VerifyLinSys::new(&coo, &x, &rhs).unwrap(); + let verify = VerifyLinSys::from(&coo, &x, &rhs).unwrap(); let mut stats = StatsLinSol::new(); mumps.update_stats(&mut stats); let s = &stats.mumps_stats; diff --git a/russell_sparse/examples/nonlinear_system_4eqs.rs b/russell_sparse/examples/nonlinear_system_4eqs.rs index 54086464..270a9461 100644 --- a/russell_sparse/examples/nonlinear_system_4eqs.rs +++ b/russell_sparse/examples/nonlinear_system_4eqs.rs @@ -29,9 +29,8 @@ fn main() -> Result<(), StrError> { let mut solver = LinSolver::new(genie)?; // allocate Jacobian matrix (J) as SparseMatrix - let one_based = if genie == Genie::Mumps { true } else { false }; let (neq, nnz) = (4, 16); - let mut jj = SparseMatrix::new_coo(neq, neq, nnz, None, one_based).unwrap(); + let mut jj = SparseMatrix::new_coo(neq, neq, nnz, None).unwrap(); // allocate residual (rr), vector of unknowns (uu), and minus-uu (mdu) let mut rr = Vector::new(neq); diff --git a/russell_sparse/src/aliases.rs b/russell_sparse/src/aliases.rs new file mode 100644 index 00000000..f31b09e4 --- /dev/null +++ b/russell_sparse/src/aliases.rs @@ -0,0 +1,26 @@ +use crate::{NumCooMatrix, NumCscMatrix, NumCsrMatrix, NumSparseMatrix}; +use num_complex::Complex64; + +/// Defines an alias to NumCooMatrix with f64 +pub type CooMatrix = NumCooMatrix; + +/// Defines an alias to NumCscMatrix with f64 +pub type CscMatrix = NumCscMatrix; + +/// Defines an alias to NumCsrMatrix with f64 +pub type CsrMatrix = NumCsrMatrix; + +/// Defines an alias to NumSparseMatrix with f64 +pub type SparseMatrix = NumSparseMatrix; + +/// Defines an alias to NumCooMatrix with Complex64 +pub type ComplexCooMatrix = NumCooMatrix; + +/// Defines an alias to NumCscMatrix with Complex64 +pub type ComplexCscMatrix = NumCscMatrix; + +/// Defines an alias to NumCsrMatrix with Complex64 +pub type ComplexCsrMatrix = NumCsrMatrix; + +/// Defines an alias to NumSparseMatrix with Complex64 +pub type ComplexSparseMatrix = NumSparseMatrix; diff --git a/russell_sparse/src/bin/mem_check.rs b/russell_sparse/src/bin/mem_check.rs index 64126257..6bc776a4 100644 --- a/russell_sparse/src/bin/mem_check.rs +++ b/russell_sparse/src/bin/mem_check.rs @@ -1,15 +1,17 @@ -use russell_lab::Vector; +use num_complex::Complex64; +use russell_lab::vec_approx_eq; +use russell_lab::{complex_vec_approx_eq, cpx, ComplexVector, Vector}; use russell_sparse::prelude::*; +use russell_sparse::Samples; fn test_solver(genie: Genie) { + println!("----------------------------------------------------------------------\n"); match genie { Genie::Mumps => println!("Testing MUMPS solver\n"), Genie::Umfpack => println!("Testing UMFPACK solver\n"), Genie::IntelDss => println!("Testing Intel DSS solver\n"), } - let (ndim, nnz) = (5, 13); - let mut solver = match LinSolver::new(genie) { Ok(v) => v, Err(e) => { @@ -18,30 +20,65 @@ fn test_solver(genie: Genie) { } }; - let one_based = if genie == Genie::Mumps { true } else { false }; - let mut coo = match SparseMatrix::new_coo(ndim, ndim, nnz, None, one_based) { + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); + let mut mat = SparseMatrix::from_coo(coo); + + match solver.actual.factorize(&mut mat, None) { + Err(e) => { + println!("FAIL(factorize): {}", e); + return; + } + _ => (), + }; + + let mut x = Vector::new(5); + let rhs = Vector::from(&[8.0, 45.0, -3.0, 3.0, 19.0]); + + match solver.actual.solve(&mut x, &mut mat, &rhs, false) { + Err(e) => { + println!("FAIL(solve): {}", e); + return; + } + _ => (), + } + + match solver.actual.solve(&mut x, &mat, &rhs, false) { + Err(e) => { + println!("FAIL(solve again): {}", e); + return; + } + _ => (), + } + + println!("x =\n{}", x); + let x_correct = &[1.0, 2.0, 3.0, 4.0, 5.0]; + vec_approx_eq(x.as_data(), x_correct, 1e-14); +} + +fn test_complex_solver(genie: Genie) { + println!("----------------------------------------------------------------------\n"); + match genie { + Genie::Mumps => println!("Testing Complex MUMPS solver\n"), + Genie::Umfpack => println!("Testing Complex UMFPACK solver\n"), + Genie::IntelDss => println!("Testing Complex Intel DSS solver\n"), + } + + let mut solver = match ComplexLinSolver::new(genie) { Ok(v) => v, Err(e) => { - println!("FAIL(new CooMatrix): {}", e); + println!("FAIL(new solver): {}", e); return; } }; - coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) - coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) - coo.put(1, 0, 3.0).unwrap(); - coo.put(0, 1, 3.0).unwrap(); - coo.put(2, 1, -1.0).unwrap(); - coo.put(4, 1, 4.0).unwrap(); - coo.put(1, 2, 4.0).unwrap(); - coo.put(2, 2, -3.0).unwrap(); - coo.put(3, 2, 1.0).unwrap(); - coo.put(4, 2, 2.0).unwrap(); - coo.put(2, 3, 2.0).unwrap(); - coo.put(1, 4, 6.0).unwrap(); - coo.put(4, 4, 1.0).unwrap(); - - match solver.actual.factorize(&mut coo, None) { + let coo = match genie { + Genie::Mumps => Samples::complex_symmetric_3x3_lower().0, + Genie::Umfpack => Samples::complex_symmetric_3x3_full().0, + Genie::IntelDss => panic!("TODO"), + }; + let mut mat = ComplexSparseMatrix::from_coo(coo); + + match solver.actual.factorize(&mut mat, None) { Err(e) => { println!("FAIL(factorize): {}", e); return; @@ -49,10 +86,10 @@ fn test_solver(genie: Genie) { _ => (), }; - let mut x = Vector::new(5); - let rhs = Vector::from(&[8.0, 45.0, -3.0, 3.0, 19.0]); + let mut x = ComplexVector::new(3); + let rhs = ComplexVector::from(&[cpx!(-3.0, 3.0), cpx!(2.0, -2.0), cpx!(9.0, 7.0)]); - match solver.actual.solve(&mut x, &mut coo, &rhs, false) { + match solver.actual.solve(&mut x, &mut mat, &rhs, false) { Err(e) => { println!("FAIL(solve): {}", e); return; @@ -60,7 +97,7 @@ fn test_solver(genie: Genie) { _ => (), } - match solver.actual.solve(&mut x, &coo, &rhs, false) { + match solver.actual.solve(&mut x, &mat, &rhs, false) { Err(e) => { println!("FAIL(solve again): {}", e); return; @@ -69,13 +106,16 @@ fn test_solver(genie: Genie) { } println!("x =\n{}", x); + let x_correct = &[cpx!(1.0, 1.0), cpx!(2.0, -2.0), cpx!(3.0, 3.0)]; + complex_vec_approx_eq(x.as_data(), x_correct, 1e-14); } fn test_solver_singular(genie: Genie) { + println!("----------------------------------------------------------------------\n"); match genie { - Genie::Mumps => println!("Testing MUMPS solver\n"), - Genie::Umfpack => println!("Testing UMFPACK solver\n"), - Genie::IntelDss => println!("Testing Intel DSS solver\n"), + Genie::Mumps => println!("Testing MUMPS solver (singular matrix)\n"), + Genie::Umfpack => println!("Testing UMFPACK solver (singular matrix)\n"), + Genie::IntelDss => println!("Testing Intel DSS solver (singular matrix)\n"), } let (ndim, nnz) = (2, 2); @@ -88,8 +128,7 @@ fn test_solver_singular(genie: Genie) { } }; - let one_based = if genie == Genie::Mumps { true } else { false }; - let mut coo_singular = match SparseMatrix::new_coo(ndim, ndim, nnz, None, one_based) { + let mut coo_singular = match SparseMatrix::new_coo(ndim, ndim, nnz, None) { Ok(v) => v, Err(e) => { println!("FAIL(new CooMatrix): {}", e); @@ -106,19 +145,21 @@ fn test_solver_singular(genie: Genie) { } fn main() { - println!("Running Mem Check\n"); - test_solver(Genie::Umfpack); - println!(""); - test_solver_singular(Genie::Umfpack); - println!("----------------------------------------------------------------------\n"); + // real test_solver(Genie::Mumps); - println!(""); - test_solver_singular(Genie::Mumps); + test_solver(Genie::Umfpack); if cfg!(with_intel_dss) { - println!("----------------------------------------------------------------------\n"); test_solver(Genie::IntelDss); - // Intel DSS cannot handle singular matrices - // test_solver_singular(Genie::IntelDss); } - println!("Done\n"); + + // complex + test_complex_solver(Genie::Mumps); + test_complex_solver(Genie::Umfpack); + + // singular real + test_solver_singular(Genie::Mumps); + test_solver_singular(Genie::Umfpack); + // Note: Intel DSS cannot handle singular matrices + + println!("----------------------------------------------------------------------\n"); } diff --git a/russell_sparse/src/bin/solve_matrix_market.rs b/russell_sparse/src/bin/solve_matrix_market.rs index 6b44dc99..de0d2208 100644 --- a/russell_sparse/src/bin/solve_matrix_market.rs +++ b/russell_sparse/src/bin/solve_matrix_market.rs @@ -70,10 +70,10 @@ fn main() -> Result<(), StrError> { let genie = Genie::from(&opt.genie); // select the symmetric handling option - let (handling, one_based) = match genie { - Genie::Mumps => (MMsymOption::LeaveAsLower, true), - Genie::Umfpack => (MMsymOption::MakeItFull, false), - Genie::IntelDss => (MMsymOption::SwapToUpper, false), + let handling = match genie { + Genie::Mumps => MMsymOption::LeaveAsLower, + Genie::Umfpack => MMsymOption::MakeItFull, + Genie::IntelDss => MMsymOption::SwapToUpper, }; // configuration parameters @@ -98,8 +98,8 @@ fn main() -> Result<(), StrError> { stats.requests.mumps_num_threads = params.mumps_num_threads; // read the matrix - let mut sw = Stopwatch::new(""); - let coo = read_matrix_market(&opt.matrix_market_file, handling, one_based)?; + let mut sw = Stopwatch::new(); + let coo = read_matrix_market(&opt.matrix_market_file, handling)?; stats.time_nanoseconds.read_matrix = sw.stop(); // save the COO matrix as a generic SparseMatrix @@ -128,7 +128,7 @@ fn main() -> Result<(), StrError> { // verify the solution sw.reset(); - stats.verify = VerifyLinSys::new(&mat, &x, &rhs)?; + stats.verify = VerifyLinSys::from(&mat, &x, &rhs)?; stats.time_nanoseconds.verify = sw.stop(); // update and print stats diff --git a/russell_sparse/src/complex_coo_matrix.rs b/russell_sparse/src/complex_coo_matrix.rs new file mode 100644 index 00000000..629ae12a --- /dev/null +++ b/russell_sparse/src/complex_coo_matrix.rs @@ -0,0 +1,206 @@ +use crate::StrError; +use crate::{ComplexCooMatrix, CooMatrix}; +use num_complex::Complex64; +use russell_lab::cpx; + +impl ComplexCooMatrix { + /// Assigns this matrix to the values of another real matrix (scaled) + /// + /// Performs: + /// + /// ```text + /// this = (α + βi) · other + /// ``` + /// + /// Thus: + /// + /// ```text + /// this[p].real = α · other[p] + /// this[p].imag = β · other[p] + /// + /// other[p] ∈ Reals + /// p = [0, nnz(other)] + /// ``` + /// + /// **Warning:** make sure to allocate `max_nnz ≥ nnz(other)`. + pub fn assign_real(&mut self, alpha: f64, beta: f64, other: &CooMatrix) -> Result<(), StrError> { + if other.nrow != self.nrow { + return Err("matrices must have the same nrow"); + } + if other.ncol != self.ncol { + return Err("matrices must have the same ncol"); + } + if other.symmetry != self.symmetry { + return Err("matrices must have the same symmetry"); + } + self.reset(); + for p in 0..other.nnz { + let i = other.indices_i[p] as usize; + let j = other.indices_j[p] as usize; + self.put(i, j, cpx!(alpha * other.values[p], beta * other.values[p]))?; + } + Ok(()) + } + + /// Augments this matrix with the entries of another real matrix (scaled) + /// + /// Effectively, performs: + /// + /// ```text + /// this += (α + βi) · other + /// ``` + /// + /// Thus: + /// + /// ```text + /// this[p].real += α · other[p] + /// this[p].imag += β · other[p] + /// + /// other[p] ∈ Reals + /// p = [0, nnz(other)] + /// ``` + /// + /// **Warning:** make sure to allocate `max_nnz ≥ nnz(this) + nnz(other)`. + pub fn augment_real(&mut self, alpha: f64, beta: f64, other: &CooMatrix) -> Result<(), StrError> { + if other.nrow != self.nrow { + return Err("matrices must have the same nrow"); + } + if other.ncol != self.ncol { + return Err("matrices must have the same ncol"); + } + if other.symmetry != self.symmetry { + return Err("matrices must have the same symmetry"); + } + for p in 0..other.nnz { + let i = other.indices_i[p] as usize; + let j = other.indices_j[p] as usize; + self.put(i, j, cpx!(alpha * other.values[p], beta * other.values[p]))?; + } + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use crate::{ComplexCooMatrix, CooMatrix, Storage, Symmetry}; + use num_complex::Complex64; + use russell_lab::cpx; + + #[test] + fn assign_capture_errors() { + let sym = Some(Symmetry::General(Storage::Full)); + let nnz_a = 1; + let nnz_b = 2; // wrong: must be ≤ nnz_a + let mut a_1x2 = ComplexCooMatrix::new(1, 2, nnz_a, None).unwrap(); + let b_2x1 = CooMatrix::new(2, 1, nnz_b, None).unwrap(); + let b_1x3 = CooMatrix::new(1, 3, nnz_b, None).unwrap(); + let b_1x2_sym = CooMatrix::new(1, 2, nnz_b, sym).unwrap(); + let mut b_1x2 = CooMatrix::new(1, 2, nnz_b, None).unwrap(); + a_1x2.put(0, 0, cpx!(123.0, 321.0)).unwrap(); + b_1x2.put(0, 0, 456.0).unwrap(); + b_1x2.put(0, 1, 654.0).unwrap(); + assert_eq!( + a_1x2.assign_real(2.0, 3.0, &b_2x1).err(), + Some("matrices must have the same nrow") + ); + assert_eq!( + a_1x2.assign_real(2.0, 3.0, &b_1x3).err(), + Some("matrices must have the same ncol") + ); + assert_eq!( + a_1x2.assign_real(2.0, 3.0, &b_1x2_sym).err(), + Some("matrices must have the same symmetry") + ); + assert_eq!( + a_1x2.assign_real(2.0, 3.0, &b_1x2).err(), + Some("COO matrix: max number of items has been reached") + ); + } + + #[test] + fn assign_works() { + let nnz = 2; + let mut a = ComplexCooMatrix::new(3, 2, nnz, None).unwrap(); + let mut b = CooMatrix::new(3, 2, nnz, None).unwrap(); + a.put(2, 1, cpx!(1000.0, 2000.0)).unwrap(); + b.put(0, 0, 10.0).unwrap(); + b.put(2, 1, 20.0).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 0+0i 0+0i │\n\ + │ 0+0i 0+0i │\n\ + │ 0+0i 1000+2000i │\n\ + └ ┘" + ); + a.assign_real(3.0, 2.0, &b).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 30+20i 0+0i │\n\ + │ 0+0i 0+0i │\n\ + │ 0+0i 60+40i │\n\ + └ ┘" + ); + } + + #[test] + fn augment_capture_errors() { + let sym = Some(Symmetry::General(Storage::Full)); + let nnz_a = 1; + let nnz_b = 1; + let mut a_1x2 = ComplexCooMatrix::new(1, 2, nnz_a /* + nnz_b */, None).unwrap(); + let b_2x1 = CooMatrix::new(2, 1, nnz_b, None).unwrap(); + let b_1x3 = CooMatrix::new(1, 3, nnz_b, None).unwrap(); + let b_1x2_sym = CooMatrix::new(1, 2, nnz_b, sym).unwrap(); + let mut b_1x2 = CooMatrix::new(1, 2, nnz_b, None).unwrap(); + a_1x2.put(0, 0, cpx!(123.0, 321.0)).unwrap(); + b_1x2.put(0, 0, 456.0).unwrap(); + assert_eq!( + a_1x2.augment_real(2.0, 3.0, &b_2x1).err(), + Some("matrices must have the same nrow") + ); + assert_eq!( + a_1x2.augment_real(2.0, 3.0, &b_1x3).err(), + Some("matrices must have the same ncol") + ); + assert_eq!( + a_1x2.augment_real(2.0, 3.0, &b_1x2_sym).err(), + Some("matrices must have the same symmetry") + ); + assert_eq!( + a_1x2.augment_real(2.0, 3.0, &b_1x2).err(), + Some("COO matrix: max number of items has been reached") + ); + } + + #[test] + fn augment_works() { + let nnz_a = 1; + let nnz_b = 2; + let mut a = ComplexCooMatrix::new(3, 2, nnz_a + nnz_b, None).unwrap(); + let mut b = CooMatrix::new(3, 2, nnz_b, None).unwrap(); + a.put(2, 1, cpx!(1000.0, 2000.0)).unwrap(); + b.put(0, 0, 10.0).unwrap(); + b.put(2, 1, 20.0).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 0+0i 0+0i │\n\ + │ 0+0i 0+0i │\n\ + │ 0+0i 1000+2000i │\n\ + └ ┘" + ); + a.augment_real(3.0, 2.0, &b).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 30+20i 0+0i │\n\ + │ 0+0i 0+0i │\n\ + │ 0+0i 1060+2040i │\n\ + └ ┘" + ); + } +} diff --git a/russell_sparse/src/complex_lin_solver.rs b/russell_sparse/src/complex_lin_solver.rs new file mode 100644 index 00000000..2db88d93 --- /dev/null +++ b/russell_sparse/src/complex_lin_solver.rs @@ -0,0 +1,158 @@ +use super::{ComplexSolverMUMPS, ComplexSolverUMFPACK, ComplexSparseMatrix, Genie, LinSolParams, StatsLinSol}; +use crate::StrError; +use russell_lab::ComplexVector; + +/// Defines a unified interface for complex linear system solvers +pub trait ComplexLinSolTrait { + /// Performs the factorization (and analysis/initialization if needed) + /// + /// # Input + /// + /// * `mat` -- The sparse matrix (COO, CSC, or CSR). + /// * `params` -- configuration parameters; None => use default + /// + /// # Notes + /// + /// 1. The structure of the matrix (nrow, ncol, nnz, symmetry) must be + /// exactly the same among multiple calls to `factorize`. The values may differ + /// from call to call, nonetheless. + /// 2. The first call to `factorize` will define the structure which must be + /// kept the same for the next calls. + /// 3. If the structure of the matrix needs to be changed, the solver must + /// be "dropped" and a new solver allocated. + fn factorize(&mut self, mat: &mut ComplexSparseMatrix, params: Option) -> Result<(), StrError>; + + /// Computes the solution of the linear system + /// + /// Solves the linear system: + /// + /// ```text + /// A · x = rhs + /// (m,n) (n) (m) + /// ``` + /// + /// # Output + /// + /// * `x` -- the vector of unknown values with dimension equal to mat.ncol + /// + /// # Input + /// + /// * `mat` -- the coefficient matrix A. + /// * `rhs` -- the right-hand side vector with know values an dimension equal to mat.ncol + /// * `verbose` -- shows messages + /// + /// **Warning:** the matrix must be same one used in `factorize`. + fn solve( + &mut self, + x: &mut ComplexVector, + mat: &ComplexSparseMatrix, + rhs: &ComplexVector, + verbose: bool, + ) -> Result<(), StrError>; + + /// Updates the stats structure (should be called after solve) + fn update_stats(&self, stats: &mut StatsLinSol); +} + +/// Unifies the access to linear system solvers +pub struct ComplexLinSolver<'a> { + /// Holds the actual implementation + pub actual: Box, +} + +impl<'a> ComplexLinSolver<'a> { + /// Allocates a new instance + /// + /// # Input + /// + /// * `genie` -- the actual implementation that does all the magic + pub fn new(genie: Genie) -> Result { + let actual: Box = match genie { + Genie::Mumps => Box::new(ComplexSolverMUMPS::new()?), + Genie::Umfpack => Box::new(ComplexSolverUMFPACK::new()?), + Genie::IntelDss => panic!("TODO"), + }; + Ok(ComplexLinSolver { actual }) + } + + /// Computes the solution of a complex linear system + /// + /// Solves the linear system: + /// + /// ```text + /// A · x = rhs + /// (m,n) (n) (m) + /// ``` + /// + /// # Output + /// + /// * `x` -- the vector of unknown values with dimension equal to mat.ncol + /// + /// # Input + /// + /// * `genie` -- the actual implementation that does all the magic + /// * `mat` -- the matrix representing the sparse coefficient matrix A (see Notes below) + /// * `rhs` -- the right-hand side vector with know values an dimension equal to coo.nrow + /// * `verbose` -- shows messages + /// + /// # Notes + /// + /// 1. For symmetric matrices, `MUMPS` requires that the symmetry/storage be Lower or Full. + /// 2. For symmetric matrices, `UMFPACK` requires that the symmetry/storage be Full. + /// 3. For symmetric matrices, `IntelDSS` requires that the symmetry/storage be Upper. + /// 4. This function calls the actual implementation (genie) via the functions `factorize`, and `solve`. + /// 5. This function is best for a **single-use**, whereas the actual + /// solver should be considered for a recurrent use (e.g., inside a loop). + pub fn compute( + genie: Genie, + x: &mut ComplexVector, + mat: &mut ComplexSparseMatrix, + rhs: &ComplexVector, + params: Option, + ) -> Result { + let mut solver = ComplexLinSolver::new(genie)?; + solver.actual.factorize(mat, params)?; + let verbose = if let Some(p) = params { p.verbose } else { false }; + solver.actual.solve(x, mat, rhs, verbose)?; + Ok(solver) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::ComplexLinSolver; + use crate::{ComplexSparseMatrix, Genie, Samples}; + use num_complex::Complex64; + use russell_lab::{complex_vec_approx_eq, cpx, ComplexVector}; + use serial_test::serial; + + #[test] + fn complex_lin_solver_new_works() { + ComplexLinSolver::new(Genie::Umfpack).unwrap(); + } + + #[test] + #[serial] + fn complex_lin_solver_compute_works_mumps() { + let (coo, _, _, _) = Samples::complex_symmetric_3x3_lower(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut x = ComplexVector::new(3); + let rhs = ComplexVector::from(&[cpx!(-3.0, 3.0), cpx!(2.0, -2.0), cpx!(9.0, 7.0)]); + ComplexLinSolver::compute(Genie::Mumps, &mut x, &mut mat, &rhs, None).unwrap(); + let x_correct = &[cpx!(1.0, 1.0), cpx!(2.0, -2.0), cpx!(3.0, 3.0)]; + complex_vec_approx_eq(x.as_data(), x_correct, 1e-15); + } + + #[test] + fn complex_lin_solver_compute_works_umfpack() { + let (coo, _, _, _) = Samples::complex_symmetric_3x3_full(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut x = ComplexVector::new(3); + let rhs = ComplexVector::from(&[cpx!(-3.0, 3.0), cpx!(2.0, -2.0), cpx!(9.0, 7.0)]); + ComplexLinSolver::compute(Genie::Umfpack, &mut x, &mut mat, &rhs, None).unwrap(); + let x_correct = &[cpx!(1.0, 1.0), cpx!(2.0, -2.0), cpx!(3.0, 3.0)]; + complex_vec_approx_eq(x.as_data(), x_correct, 1e-15); + } +} diff --git a/russell_sparse/src/complex_solver_mumps.rs b/russell_sparse/src/complex_solver_mumps.rs new file mode 100644 index 00000000..dd8df0eb --- /dev/null +++ b/russell_sparse/src/complex_solver_mumps.rs @@ -0,0 +1,692 @@ +use super::{handle_mumps_error_code, mumps_ordering, mumps_scaling}; +use super::{ComplexLinSolTrait, ComplexSparseMatrix, LinSolParams, StatsLinSol, Symmetry}; +use super::{ + MUMPS_ORDERING_AMD, MUMPS_ORDERING_AMF, MUMPS_ORDERING_AUTO, MUMPS_ORDERING_METIS, MUMPS_ORDERING_PORD, + MUMPS_ORDERING_QAMD, MUMPS_ORDERING_SCOTCH, MUMPS_SCALING_AUTO, MUMPS_SCALING_COLUMN, MUMPS_SCALING_DIAGONAL, + MUMPS_SCALING_NO, MUMPS_SCALING_ROW_COL, MUMPS_SCALING_ROW_COL_ITER, MUMPS_SCALING_ROW_COL_RIG, +}; +use crate::auxiliary_and_constants::*; +use crate::StrError; +use num_complex::Complex64; +use russell_lab::{complex_vec_copy, using_intel_mkl, ComplexVector, Stopwatch}; + +/// Opaque struct holding a C-pointer to InterfaceComplexMUMPS +/// +/// Reference: +#[repr(C)] +struct InterfaceComplexMUMPS { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} + +/// Enforce Send on the C structure +/// +/// +unsafe impl Send for InterfaceComplexMUMPS {} + +/// Enforce Send on the Rust structure +/// +/// +unsafe impl Send for ComplexSolverMUMPS {} + +extern "C" { + fn complex_solver_mumps_new() -> *mut InterfaceComplexMUMPS; + fn complex_solver_mumps_drop(solver: *mut InterfaceComplexMUMPS); + fn complex_solver_mumps_initialize( + solver: *mut InterfaceComplexMUMPS, + ordering: i32, + scaling: i32, + pct_inc_workspace: i32, + max_work_memory: i32, + openmp_num_threads: i32, + verbose: CcBool, + general_symmetric: CcBool, + positive_definite: CcBool, + ndim: i32, + nnz: i32, + indices_i: *const i32, + indices_j: *const i32, + values_aij: *const Complex64, + ) -> i32; + fn complex_solver_mumps_factorize( + solver: *mut InterfaceComplexMUMPS, + effective_ordering: *mut i32, + effective_scaling: *mut i32, + determinant_coefficient_real: *mut f64, + determinant_coefficient_imag: *mut f64, + determinant_exponent: *mut f64, + compute_determinant: CcBool, + verbose: CcBool, + ) -> i32; + fn complex_solver_mumps_solve( + solver: *mut InterfaceComplexMUMPS, + rhs: *mut Complex64, + error_analysis_array_len_8: *mut f64, + error_analysis_option: i32, + verbose: CcBool, + ) -> i32; +} + +/// Wraps the MUMPS solver for (very large) sparse linear systems +/// +/// **Warning:** This solver is **not** thread-safe, thus use only use in single-thread applications. +pub struct ComplexSolverMUMPS { + /// Holds a pointer to the C interface to MUMPS + solver: *mut InterfaceComplexMUMPS, + + /// Indicates whether the solver has been initialized or not (just once) + initialized: bool, + + /// Indicates whether the sparse matrix has been factorized or not + factorized: bool, + + /// Holds the symmetry type used in the initialize + initialized_symmetry: Symmetry, + + /// Holds the matrix dimension saved in initialize + initialized_ndim: usize, + + /// Holds the number of non-zeros saved in initialize + initialized_nnz: usize, + + /// Holds the used ordering (after factorize) + effective_ordering: i32, + + /// Holds the used scaling (after factorize) + effective_scaling: i32, + + /// Holds the OpenMP number of threads passed down to MUMPS (ICNTL(16)) + effective_num_threads: i32, + + /// Holds the determinant coefficient (if requested) (real part) + /// + /// det = coefficient * pow(2, exponent) + determinant_coefficient_real: f64, + + /// Holds the determinant coefficient (if requested) (imaginary part) + /// + /// det = coefficient * pow(2, exponent) + determinant_coefficient_imag: f64, + + /// Holds the determinant exponent (if requested) + /// + /// det = coefficient * pow(2, exponent) + determinant_exponent: f64, + + /// MUMPS code for error analysis (after solve) + /// + /// ICNTL(11): 0 (nothing), 1 (all; slow), 2 (just errors) + error_analysis_option: i32, + + /// Holds the error analysis "stat" results + error_analysis_array_len_8: Vec, + + /// Stopwatch to measure computation times + stopwatch: Stopwatch, + + /// Time spent on initialize in nanoseconds + time_initialize_ns: u128, + + /// Time spent on factorize in nanoseconds + time_factorize_ns: u128, + + /// Time spent on solve in nanoseconds + time_solve_ns: u128, + + /// Holds the (one-based/Fortran) row indices i + fortran_indices_i: Vec, + + /// Holds the (one-based/Fortran) column indices j + fortran_indices_j: Vec, +} + +impl Drop for ComplexSolverMUMPS { + /// Tells the c-code to release memory + fn drop(&mut self) { + unsafe { + complex_solver_mumps_drop(self.solver); + } + } +} + +impl ComplexSolverMUMPS { + /// Allocates a new instance + pub fn new() -> Result { + unsafe { + let solver = complex_solver_mumps_new(); + if solver.is_null() { + return Err("c-code failed to allocate the MUMPS solver"); + } + Ok(ComplexSolverMUMPS { + solver, + initialized: false, + factorized: false, + initialized_symmetry: Symmetry::No, + initialized_ndim: 0, + initialized_nnz: 0, + effective_ordering: -1, + effective_scaling: -1, + effective_num_threads: 0, + determinant_coefficient_real: 0.0, + determinant_coefficient_imag: 0.0, + determinant_exponent: 0.0, + error_analysis_option: 0, + error_analysis_array_len_8: vec![0.0; 8], + stopwatch: Stopwatch::new(), + time_initialize_ns: 0, + time_factorize_ns: 0, + time_solve_ns: 0, + fortran_indices_i: Vec::new(), + fortran_indices_j: Vec::new(), + }) + } + } +} + +impl ComplexLinSolTrait for ComplexSolverMUMPS { + /// Performs the factorization (and analysis/initialization if needed) + /// + /// # Input + /// + /// * `mat` -- the coefficient matrix A (one-base **COO** only, not CSC and not CSR). + /// Also, the matrix must be square (`nrow = ncol`) and, if symmetric, + /// the symmetry/storage must [crate::Storage::Lower]. + /// * `params` -- configuration parameters; None => use default + /// + /// # Notes + /// + /// 1. The structure of the matrix (nrow, ncol, nnz, symmetry) must be + /// exactly the same among multiple calls to `factorize`. The values may differ + /// from call to call, nonetheless. + /// 2. The first call to `factorize` will define the structure which must be + /// kept the same for the next calls. + /// 3. If the structure of the matrix needs to be changed, the solver must + /// be "dropped" and a new solver allocated. + /// 4. For symmetric matrices, `MUMPS` requires that the symmetry/storage be [crate::Storage::Lower]. + /// 5. The COO matrix must be one-based. + fn factorize(&mut self, mat: &mut ComplexSparseMatrix, params: Option) -> Result<(), StrError> { + // get COO matrix + let coo = mat.get_coo()?; + + // check the COO matrix + if coo.nrow != coo.ncol { + return Err("the COO matrix must be square"); + } + if coo.nnz < 1 { + return Err("the COO matrix must have at least one non-zero value"); + } + + // check already initialized data + if self.initialized { + if coo.symmetry != self.initialized_symmetry { + return Err("subsequent factorizations must use the same matrix (symmetry differs)"); + } + if coo.nrow != self.initialized_ndim { + return Err("subsequent factorizations must use the same matrix (ndim differs)"); + } + if coo.nnz != self.initialized_nnz { + return Err("subsequent factorizations must use the same matrix (nnz differs)"); + } + } else { + self.initialized_symmetry = coo.symmetry; + self.initialized_ndim = coo.nrow; + self.initialized_nnz = coo.nnz; + self.fortran_indices_i = vec![0; coo.nnz]; + self.fortran_indices_j = vec![0; coo.nnz]; + for k in 0..coo.nnz { + self.fortran_indices_i[k] = coo.indices_i[k] + 1; + self.fortran_indices_j[k] = coo.indices_j[k] + 1; + } + } + + // configuration parameters + let par = if let Some(p) = params { p } else { LinSolParams::new() }; + + // error analysis option + self.error_analysis_option = if par.compute_condition_numbers { + 1 // all the statistics (very expensive) (page 40) + } else if par.compute_error_estimates { + 2 // main statistics are computed (page 40) + } else { + 0 // nothing + }; + + // input parameters + let ordering = mumps_ordering(par.ordering); + let scaling = mumps_scaling(par.scaling); + let pct_inc_workspace = to_i32(par.mumps_pct_inc_workspace); + let max_work_memory = to_i32(par.mumps_max_work_memory); + self.effective_num_threads = + if using_intel_mkl() || par.mumps_num_threads != 0 || par.mumps_override_prevent_nt_issue_with_openblas { + to_i32(par.mumps_num_threads) + } else { + 1 // avoid bug with OpenBLAS + }; + + // requests + let compute_determinant = if par.compute_determinant { 1 } else { 0 }; + let verbose = if par.verbose { 1 } else { 0 }; + + // extract the symmetry flags and check the storage type + let (general_symmetric, positive_definite) = coo.symmetry.status(true, false)?; + + // matrix config + let ndim = to_i32(coo.nrow); + let nnz = to_i32(coo.nnz); + + // call initialize just once + if !self.initialized { + self.stopwatch.reset(); + unsafe { + let status = complex_solver_mumps_initialize( + self.solver, + ordering, + scaling, + pct_inc_workspace, + max_work_memory, + self.effective_num_threads, + verbose, + general_symmetric, + positive_definite, + ndim, + nnz, + self.fortran_indices_i.as_ptr(), + self.fortran_indices_j.as_ptr(), + coo.values.as_ptr(), + ); + if status != SUCCESSFUL_EXIT { + return Err(handle_mumps_error_code(status)); + } + } + self.time_initialize_ns = self.stopwatch.stop(); + self.initialized = true; + } + + // call factorize + self.stopwatch.reset(); + unsafe { + let status = complex_solver_mumps_factorize( + self.solver, + &mut self.effective_ordering, + &mut self.effective_scaling, + &mut self.determinant_coefficient_real, + &mut self.determinant_coefficient_imag, + &mut self.determinant_exponent, + compute_determinant, + verbose, + ); + if status != SUCCESSFUL_EXIT { + return Err(handle_mumps_error_code(status)); + } + } + self.time_factorize_ns = self.stopwatch.stop(); + + // done + self.factorized = true; + Ok(()) + } + + /// Computes the solution of the linear system + /// + /// Solves the linear system: + /// + /// ```text + /// A · x = rhs + /// (m,m) (m) (m) + /// ``` + /// + /// # Output + /// + /// * `x` -- the vector of unknown values with dimension equal to mat.nrow + /// + /// # Input + /// + /// * `mat` -- the coefficient matrix A; must be square and, if symmetric, [crate::Storage::Lower]. + /// * `rhs` -- the right-hand side vector with know values an dimension equal to mat.nrow + /// * `verbose` -- shows messages + /// + /// **Warning:** the matrix must be same one used in `factorize`. + fn solve( + &mut self, + x: &mut ComplexVector, + mat: &ComplexSparseMatrix, + rhs: &ComplexVector, + verbose: bool, + ) -> Result<(), StrError> { + // check + if !self.factorized { + return Err("the function factorize must be called before solve"); + } + + // access COO matrix + let coo = mat.get_coo()?; + + // check already factorized data + let (nrow, ncol, nnz, symmetry) = coo.get_info(); + if symmetry != self.initialized_symmetry { + return Err("solve must use the same matrix (symmetry differs)"); + } + if nrow != self.initialized_ndim || ncol != self.initialized_ndim { + return Err("solve must use the same matrix (ndim differs)"); + } + if nnz != self.initialized_nnz { + return Err("solve must use the same matrix (nnz differs)"); + } + + // check vectors + if x.dim() != self.initialized_ndim { + return Err("the dimension of the vector of unknown values x is incorrect"); + } + if rhs.dim() != self.initialized_ndim { + return Err("the dimension of the right-hand side vector is incorrect"); + } + + // call MUMPS solve + complex_vec_copy(x, rhs).unwrap(); + let verb = if verbose { 1 } else { 0 }; + self.stopwatch.reset(); + unsafe { + let status = complex_solver_mumps_solve( + self.solver, + x.as_mut_data().as_mut_ptr(), + self.error_analysis_array_len_8.as_mut_ptr(), + self.error_analysis_option, + verb, + ); + if status != SUCCESSFUL_EXIT { + return Err(handle_mumps_error_code(status)); + } + } + self.time_solve_ns = self.stopwatch.stop(); + + // done + Ok(()) + } + + /// Updates the stats structure (should be called after solve) + fn update_stats(&self, stats: &mut StatsLinSol) { + stats.main.solver = if cfg!(local_mumps) { + "MUMPS-local".to_string() + } else { + "MUMPS".to_string() + }; + stats.determinant.mantissa_real = self.determinant_coefficient_real; + stats.determinant.mantissa_imag = self.determinant_coefficient_imag; + stats.determinant.base = 2.0; + stats.determinant.exponent = self.determinant_exponent; + stats.output.effective_ordering = match self.effective_ordering { + MUMPS_ORDERING_AMD => "Amd".to_string(), + MUMPS_ORDERING_AMF => "Amf".to_string(), + MUMPS_ORDERING_AUTO => "Auto".to_string(), + MUMPS_ORDERING_METIS => "Metis".to_string(), + MUMPS_ORDERING_PORD => "Pord".to_string(), + MUMPS_ORDERING_QAMD => "Qamd".to_string(), + MUMPS_ORDERING_SCOTCH => "Scotch".to_string(), + _ => "Unknown".to_string(), + }; + stats.output.effective_scaling = match self.effective_scaling { + MUMPS_SCALING_AUTO => "Auto".to_string(), + MUMPS_SCALING_COLUMN => "Column".to_string(), + MUMPS_SCALING_DIAGONAL => "Diagonal".to_string(), + MUMPS_SCALING_NO => "No".to_string(), + MUMPS_SCALING_ROW_COL => "RowCol".to_string(), + MUMPS_SCALING_ROW_COL_ITER => "RowColIter".to_string(), + MUMPS_SCALING_ROW_COL_RIG => "RowColRig".to_string(), + -2 => "Scaling done during analysis".to_string(), + _ => "Unknown".to_string(), + }; + stats.output.effective_mumps_num_threads = self.effective_num_threads as usize; + stats.mumps_stats.inf_norm_a = self.error_analysis_array_len_8[0]; + stats.mumps_stats.inf_norm_x = self.error_analysis_array_len_8[1]; + stats.mumps_stats.scaled_residual = self.error_analysis_array_len_8[2]; + stats.mumps_stats.backward_error_omega1 = self.error_analysis_array_len_8[3]; + stats.mumps_stats.backward_error_omega2 = self.error_analysis_array_len_8[4]; + stats.mumps_stats.normalized_delta_x = self.error_analysis_array_len_8[5]; + stats.mumps_stats.condition_number1 = self.error_analysis_array_len_8[6]; + stats.mumps_stats.condition_number2 = self.error_analysis_array_len_8[7]; + stats.time_nanoseconds.initialize = self.time_initialize_ns; + stats.time_nanoseconds.factorize = self.time_factorize_ns; + stats.time_nanoseconds.solve = self.time_solve_ns; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ComplexCooMatrix, ComplexSparseMatrix, LinSolParams, Ordering, Samples, Scaling, Storage, Symmetry}; + use num_complex::Complex64; + use russell_lab::{complex_approx_eq, complex_vec_approx_eq, cpx, ComplexVector}; + use serial_test::serial; + + // IMPORTANT: + // Since MUMPS is not thread-safe, we need to use serial_test::serial + + #[test] + #[serial] + fn factorize_handles_errors() { + // allocate a new solver + let mut solver = ComplexSolverMUMPS::new().unwrap(); + assert!(!solver.factorized); + + // get COO matrix errors + let (_, csc, _, _) = Samples::complex_symmetric_3x3_full(); + let mut mat = ComplexSparseMatrix::from_csc(csc); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("COO matrix is not available") + ); + + // check COO matrix + let mut coo = ComplexCooMatrix::new(2, 1, 1, None).unwrap(); + coo.put(0, 0, cpx!(4.0, 4.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("the COO matrix must be square") + ); + let coo = ComplexCooMatrix::new(1, 1, 1, None).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("the COO matrix must have at least one non-zero value") + ); + let mut coo = ComplexCooMatrix::new(1, 1, 1, Some(Symmetry::General(Storage::Full))).unwrap(); + coo.put(0, 0, cpx!(4.0, 4.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("if the matrix is general symmetric, the required storage is lower triangular") + ); + + // check already factorized data + let mut coo = ComplexCooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + coo.put(1, 1, cpx!(2.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + // ... factorize once => OK + solver.factorize(&mut mat, None).unwrap(); + // ... change matrix (symmetry) + let mut coo = ComplexCooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + coo.put(1, 1, cpx!(2.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("subsequent factorizations must use the same matrix (symmetry differs)") + ); + // ... change matrix (ndim) + let mut coo = ComplexCooMatrix::new(1, 1, 1, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("subsequent factorizations must use the same matrix (ndim differs)") + ); + // ... change matrix (nnz) + let mut coo = ComplexCooMatrix::new(2, 2, 1, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("subsequent factorizations must use the same matrix (nnz differs)") + ); + } + + #[test] + #[serial] + fn factorize_fails_on_singular_matrix() { + let mut mat_singular = ComplexSparseMatrix::new_coo(3, 3, 2, None).unwrap(); + mat_singular.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + mat_singular.put(1, 1, cpx!(1.0, 0.0)).unwrap(); + let mut solver = ComplexSolverMUMPS::new().unwrap(); + assert_eq!( + solver.factorize(&mut mat_singular, None), + Err("Error(-10): numerically singular matrix") + ); + } + + #[test] + #[serial] + fn solve_handles_errors() { + let mut coo = ComplexCooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + coo.put(1, 1, cpx!(456.0, 2.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut solver = ComplexSolverMUMPS::new().unwrap(); + assert!(!solver.factorized); + let mut x = ComplexVector::new(2); + let rhs = ComplexVector::new(2); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the function factorize must be called before solve") + ); + let mut x = ComplexVector::new(1); + solver.factorize(&mut mat, None).unwrap(); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the dimension of the vector of unknown values x is incorrect") + ); + let mut x = ComplexVector::new(2); + let rhs = ComplexVector::new(1); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the dimension of the right-hand side vector is incorrect") + ); + // wrong symmetry + let rhs = ComplexVector::new(2); + let mut coo_wrong = ComplexCooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo_wrong.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + coo_wrong.put(1, 1, cpx!(456.0, 2.0)).unwrap(); + let mut mat_wrong = ComplexSparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (symmetry differs)") + ); + // wrong ndim + let mut coo_wrong = ComplexCooMatrix::new(1, 1, 1, None).unwrap(); + coo_wrong.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + let mut mat_wrong = ComplexSparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (ndim differs)") + ); + // wrong nnz + let mut coo_wrong = ComplexCooMatrix::new(2, 2, 3, None).unwrap(); + coo_wrong.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + coo_wrong.put(1, 1, cpx!(456.0, 2.0)).unwrap(); + coo_wrong.put(0, 1, cpx!(100.0, 1.0)).unwrap(); + let mut mat_wrong = ComplexSparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (nnz differs)") + ); + } + + #[test] + #[serial] + fn factorize_and_solve_work() { + // allocate x and rhs + let mut x = ComplexVector::new(3); + let rhs = ComplexVector::from(&[cpx!(-3.0, 3.0), cpx!(2.0, -2.0), cpx!(9.0, 7.0)]); + let x_correct = &[cpx!(1.0, 1.0), cpx!(2.0, -2.0), cpx!(3.0, 3.0)]; + + // allocate a new solver + let mut solver = ComplexSolverMUMPS::new().unwrap(); + assert!(!solver.factorized); + + // sample matrix + let (coo, _, _, _) = Samples::complex_symmetric_3x3_lower(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + + // set params + let mut params = LinSolParams::new(); + params.ordering = Ordering::Pord; + params.scaling = Scaling::RowCol; + params.compute_determinant = true; + + // factorize works + solver.factorize(&mut mat, Some(params)).unwrap(); + assert!(solver.factorized); + + // solve works + solver.solve(&mut x, &mat, &rhs, false).unwrap(); + complex_vec_approx_eq(x.as_data(), x_correct, 1e-14); + + // check ordering and scaling + assert_eq!(solver.effective_ordering, 4); // Pord + assert_eq!(solver.effective_scaling, 0); // No, because we requested the determinant + + // check the determinant + let m = cpx!(solver.determinant_coefficient_real, solver.determinant_coefficient_imag); + let det = m * f64::powf(2.0, solver.determinant_exponent); + complex_approx_eq(det, cpx!(6.0, 10.0), 1e-14); + + // update stats + let mut stats = StatsLinSol::new(); + solver.update_stats(&mut stats); + assert_eq!(stats.output.effective_ordering, "Pord"); + assert_eq!(stats.output.effective_scaling, "No"); + + // calling solve again works + let mut x_again = ComplexVector::new(3); + solver.solve(&mut x_again, &mat, &rhs, false).unwrap(); + complex_vec_approx_eq(x_again.as_data(), x_correct, 1e-14); + + // solve with positive-definite matrix works + let sym = Some(Symmetry::PositiveDefinite(Storage::Lower)); + let nrow = 5; + let ncol = 5; + let mut coo_pd_lower = ComplexCooMatrix::new(nrow, ncol, 9, sym).unwrap(); + coo_pd_lower.put(0, 0, cpx!(9.0, 0.0)).unwrap(); + coo_pd_lower.put(1, 1, cpx!(0.5, 0.0)).unwrap(); + coo_pd_lower.put(2, 2, cpx!(12.0, 0.0)).unwrap(); + coo_pd_lower.put(3, 3, cpx!(0.625, 0.0)).unwrap(); + coo_pd_lower.put(4, 4, cpx!(16.0, 0.0)).unwrap(); + coo_pd_lower.put(1, 0, cpx!(1.5, 0.0)).unwrap(); + coo_pd_lower.put(2, 0, cpx!(6.0, 0.0)).unwrap(); + coo_pd_lower.put(3, 0, cpx!(0.75, 0.0)).unwrap(); + coo_pd_lower.put(4, 0, cpx!(3.0, 0.0)).unwrap(); + let mut mat_pd_lower = ComplexSparseMatrix::from_coo(coo_pd_lower); + params.ordering = Ordering::Auto; + params.scaling = Scaling::Auto; + let mut solver = ComplexSolverMUMPS::new().unwrap(); + assert!(!solver.factorized); + solver.factorize(&mut mat_pd_lower, Some(params)).unwrap(); + let mut x = ComplexVector::new(5); + let rhs = ComplexVector::from(&[1.0, 2.0, 3.0, 4.0, 5.0]); + solver.solve(&mut x, &mat_pd_lower, &rhs, false).unwrap(); + let x_correct = &[ + cpx!(-979.0 / 3.0, 0.0), + cpx!(983.0, 0.0), + cpx!(1961.0 / 12.0, 0.0), + cpx!(398.0, 0.0), + cpx!(123.0 / 2.0, 0.0), + ]; + complex_vec_approx_eq(x.as_data(), x_correct, 1e-10); + } +} diff --git a/russell_sparse/src/complex_solver_umfpack.rs b/russell_sparse/src/complex_solver_umfpack.rs new file mode 100644 index 00000000..535b8d49 --- /dev/null +++ b/russell_sparse/src/complex_solver_umfpack.rs @@ -0,0 +1,606 @@ +use super::{handle_umfpack_error_code, umfpack_ordering, umfpack_scaling}; +use super::{ComplexLinSolTrait, ComplexSparseMatrix, LinSolParams, StatsLinSol, Symmetry}; +use super::{ + UMFPACK_ORDERING_AMD, UMFPACK_ORDERING_BEST, UMFPACK_ORDERING_CHOLMOD, UMFPACK_ORDERING_METIS, + UMFPACK_ORDERING_NONE, UMFPACK_SCALE_MAX, UMFPACK_SCALE_NONE, UMFPACK_SCALE_SUM, UMFPACK_STRATEGY_AUTO, + UMFPACK_STRATEGY_SYMMETRIC, UMFPACK_STRATEGY_UNSYMMETRIC, +}; +use crate::auxiliary_and_constants::*; +use crate::StrError; +use num_complex::Complex64; +use russell_lab::{ComplexVector, Stopwatch}; + +/// Opaque struct holding a C-pointer to InterfaceComplexUMFPACK +/// +/// Reference: +#[repr(C)] +struct InterfaceComplexUMFPACK { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} + +/// Enforce Send on the C structure +/// +/// +unsafe impl Send for InterfaceComplexUMFPACK {} + +/// Enforce Send on the Rust structure +/// +/// +unsafe impl Send for ComplexSolverUMFPACK {} + +extern "C" { + fn complex_solver_umfpack_new() -> *mut InterfaceComplexUMFPACK; + fn complex_solver_umfpack_drop(solver: *mut InterfaceComplexUMFPACK); + fn complex_solver_umfpack_initialize( + solver: *mut InterfaceComplexUMFPACK, + ordering: i32, + scaling: i32, + verbose: CcBool, + enforce_unsymmetric_strategy: CcBool, + ndim: i32, + col_pointers: *const i32, + row_indices: *const i32, + values: *const Complex64, + ) -> i32; + fn complex_solver_umfpack_factorize( + solver: *mut InterfaceComplexUMFPACK, + effective_strategy: *mut i32, + effective_ordering: *mut i32, + effective_scaling: *mut i32, + rcond_estimate: *mut f64, + determinant_coefficient_real: *mut f64, + determinant_coefficient_imag: *mut f64, + determinant_exponent: *mut f64, + compute_determinant: CcBool, + verbose: CcBool, + col_pointers: *const i32, + row_indices: *const i32, + values: *const Complex64, + ) -> i32; + fn complex_solver_umfpack_solve( + solver: *mut InterfaceComplexUMFPACK, + x: *mut Complex64, + rhs: *const Complex64, + col_pointers: *const i32, + row_indices: *const i32, + values: *const Complex64, + verbose: CcBool, + ) -> i32; +} + +/// Wraps the UMFPACK solver for sparse linear systems +/// +/// **Warning:** This solver may "run out of memory" for very large matrices. +pub struct ComplexSolverUMFPACK { + /// Holds a pointer to the C interface to UMFPACK + solver: *mut InterfaceComplexUMFPACK, + + /// Indicates whether the solver has been initialized or not (just once) + initialized: bool, + + /// Indicates whether the sparse matrix has been factorized or not + factorized: bool, + + /// Holds the symmetry type used in initialize + initialized_symmetry: Symmetry, + + /// Holds the matrix dimension saved in initialize + initialized_ndim: usize, + + /// Holds the number of non-zeros saved in initialize + initialized_nnz: usize, + + /// Holds the used strategy (after factorize) + effective_strategy: i32, + + /// Holds the used ordering (after factorize) + effective_ordering: i32, + + /// Holds the used scaling (after factorize) + effective_scaling: i32, + + /// Reciprocal condition number estimate (after factorize) + rcond_estimate: f64, + + /// Holds the determinant coefficient (if requested) (real part) + /// + /// det = coefficient * pow(10, exponent) + determinant_coefficient_real: f64, + + /// Holds the determinant coefficient (if requested) (imaginary part) + /// + /// det = coefficient * pow(10, exponent) + determinant_coefficient_imag: f64, + + /// Holds the determinant exponent (if requested) + /// + /// det = coefficient * pow(10, exponent) + determinant_exponent: f64, + + /// Stopwatch to measure computation times + stopwatch: Stopwatch, + + /// Time spent on initialize in nanoseconds + time_initialize_ns: u128, + + /// Time spent on factorize in nanoseconds + time_factorize_ns: u128, + + /// Time spent on solve in nanoseconds + time_solve_ns: u128, +} + +impl Drop for ComplexSolverUMFPACK { + /// Tells the c-code to release memory + fn drop(&mut self) { + unsafe { + complex_solver_umfpack_drop(self.solver); + } + } +} + +impl ComplexSolverUMFPACK { + /// Allocates a new instance + pub fn new() -> Result { + unsafe { + let solver = complex_solver_umfpack_new(); + if solver.is_null() { + return Err("c-code failed to allocate the UMFPACK solver"); + } + Ok(ComplexSolverUMFPACK { + solver, + initialized: false, + factorized: false, + initialized_symmetry: Symmetry::No, + initialized_ndim: 0, + initialized_nnz: 0, + effective_strategy: -1, + effective_ordering: -1, + effective_scaling: -1, + rcond_estimate: 0.0, + determinant_coefficient_real: 0.0, + determinant_coefficient_imag: 0.0, + determinant_exponent: 0.0, + stopwatch: Stopwatch::new(), + time_initialize_ns: 0, + time_factorize_ns: 0, + time_solve_ns: 0, + }) + } + } +} + +impl ComplexLinSolTrait for ComplexSolverUMFPACK { + /// Performs the factorization (and analysis/initialization if needed) + /// + /// # Input + /// + /// * `mat` -- the coefficient matrix A (**COO** or **CSC**, but not CSR). + /// Also, the matrix must be square (`nrow = ncol`) and, if symmetric, + /// the symmetry/storage must [crate::Storage::Full]. + /// * `params` -- configuration parameters; None => use default + /// + /// # Notes + /// + /// 1. The structure of the matrix (nrow, ncol, nnz, symmetry) must be + /// exactly the same among multiple calls to `factorize`. The values may differ + /// from call to call, nonetheless. + /// 2. The first call to `factorize` will define the structure which must be + /// kept the same for the next calls. + /// 3. If the structure of the matrix needs to be changed, the solver must + /// be "dropped" and a new solver allocated. + /// 4. For symmetric matrices, `UMFPACK` requires that the symmetry/storage be [crate::Storage::Full]. + fn factorize(&mut self, mat: &mut ComplexSparseMatrix, params: Option) -> Result<(), StrError> { + // get CSC matrix + // (or convert from COO if CSC is not available and COO is available) + let csc = mat.get_csc_or_from_coo()?; + + // check CSC matrix + if csc.nrow != csc.ncol { + return Err("the matrix must be square"); + } + if csc.symmetry.triangular() { + return Err("for UMFPACK, the matrix must not be triangular"); + } + + // check already initialized data + if self.initialized { + if csc.symmetry != self.initialized_symmetry { + return Err("subsequent factorizations must use the same matrix (symmetry differs)"); + } + if csc.nrow != self.initialized_ndim { + return Err("subsequent factorizations must use the same matrix (ndim differs)"); + } + if (csc.col_pointers[csc.ncol] as usize) != self.initialized_nnz { + return Err("subsequent factorizations must use the same matrix (nnz differs)"); + } + } else { + self.initialized_symmetry = csc.symmetry; + self.initialized_ndim = csc.nrow; + self.initialized_nnz = csc.col_pointers[csc.ncol] as usize; + } + + // parameters + let par = if let Some(p) = params { p } else { LinSolParams::new() }; + + // input parameters + let ordering = umfpack_ordering(par.ordering); + let scaling = umfpack_scaling(par.scaling); + + // requests + let compute_determinant = if par.compute_determinant { 1 } else { 0 }; + let verbose = if par.verbose { 1 } else { 0 }; + + // matrix config + let enforce_unsym = if par.umfpack_enforce_unsymmetric_strategy { 1 } else { 0 }; + let ndim = to_i32(csc.nrow); + + // call initialize just once + if !self.initialized { + self.stopwatch.reset(); + unsafe { + let status = complex_solver_umfpack_initialize( + self.solver, + ordering, + scaling, + verbose, + enforce_unsym, + ndim, + csc.col_pointers.as_ptr(), + csc.row_indices.as_ptr(), + csc.values.as_ptr(), + ); + if status != SUCCESSFUL_EXIT { + return Err(handle_umfpack_error_code(status)); + } + } + self.time_initialize_ns = self.stopwatch.stop(); + self.initialized = true; + } + + // call factorize + self.stopwatch.reset(); + unsafe { + let status = complex_solver_umfpack_factorize( + self.solver, + &mut self.effective_strategy, + &mut self.effective_ordering, + &mut self.effective_scaling, + &mut self.rcond_estimate, + &mut self.determinant_coefficient_real, + &mut self.determinant_coefficient_imag, + &mut self.determinant_exponent, + compute_determinant, + verbose, + csc.col_pointers.as_ptr(), + csc.row_indices.as_ptr(), + csc.values.as_ptr(), + ); + if status != SUCCESSFUL_EXIT { + return Err(handle_umfpack_error_code(status)); + } + } + self.time_factorize_ns = self.stopwatch.stop(); + + // done + self.factorized = true; + Ok(()) + } + + /// Computes the solution of the linear system + /// + /// Solves the linear system: + /// + /// ```text + /// A · x = rhs + /// (m,m) (m) (m) + /// ``` + /// + /// # Output + /// + /// * `x` -- the vector of unknown values with dimension equal to mat.nrow + /// + /// # Input + /// + /// * `mat` -- the coefficient matrix A; must be square and, if symmetric, [crate::Storage::Full]. + /// * `rhs` -- the right-hand side vector with know values an dimension equal to mat.nrow + /// * `verbose` -- shows messages + /// + /// **Warning:** the matrix must be same one used in `factorize`. + fn solve( + &mut self, + x: &mut ComplexVector, + mat: &ComplexSparseMatrix, + rhs: &ComplexVector, + verbose: bool, + ) -> Result<(), StrError> { + // check + if !self.factorized { + return Err("the function factorize must be called before solve"); + } + + // access CSC matrix + // (possibly already converted from COO, because factorize was (should have been) called) + let csc = mat.get_csc()?; + + // check already factorized data + let (nrow, ncol, nnz, symmetry) = csc.get_info(); + if symmetry != self.initialized_symmetry { + return Err("solve must use the same matrix (symmetry differs)"); + } + if nrow != self.initialized_ndim || ncol != self.initialized_ndim { + return Err("solve must use the same matrix (ndim differs)"); + } + if nnz != self.initialized_nnz { + return Err("solve must use the same matrix (nnz differs)"); + } + + // check vectors + if x.dim() != self.initialized_ndim { + return Err("the dimension of the vector of unknown values x is incorrect"); + } + if rhs.dim() != self.initialized_ndim { + return Err("the dimension of the right-hand side vector is incorrect"); + } + + // call UMFPACK solve + let verb = if verbose { 1 } else { 0 }; + self.stopwatch.reset(); + unsafe { + let status = complex_solver_umfpack_solve( + self.solver, + x.as_mut_data().as_mut_ptr(), + rhs.as_data().as_ptr(), + csc.col_pointers.as_ptr(), + csc.row_indices.as_ptr(), + csc.values.as_ptr(), + verb, + ); + if status != SUCCESSFUL_EXIT { + return Err(handle_umfpack_error_code(status)); + } + } + self.time_solve_ns = self.stopwatch.stop(); + + // done + Ok(()) + } + + /// Updates the stats structure (should be called after solve) + fn update_stats(&self, stats: &mut StatsLinSol) { + stats.main.solver = if cfg!(local_umfpack) { + "UMFPACK-local".to_string() + } else { + "UMFPACK".to_string() + }; + stats.determinant.mantissa_real = self.determinant_coefficient_real; + stats.determinant.mantissa_imag = self.determinant_coefficient_imag; + stats.determinant.base = 10.0; + stats.determinant.exponent = self.determinant_exponent; + stats.output.umfpack_rcond_estimate = self.rcond_estimate; + stats.output.effective_ordering = match self.effective_ordering { + UMFPACK_ORDERING_CHOLMOD => "Cholmod".to_string(), + UMFPACK_ORDERING_AMD => "Amd".to_string(), + UMFPACK_ORDERING_METIS => "Metis".to_string(), + UMFPACK_ORDERING_BEST => "Best".to_string(), + UMFPACK_ORDERING_NONE => "No".to_string(), + _ => "Unknown".to_string(), + }; + stats.output.effective_scaling = match self.effective_scaling { + UMFPACK_SCALE_NONE => "No".to_string(), + UMFPACK_SCALE_SUM => "Sum".to_string(), + UMFPACK_SCALE_MAX => "Max".to_string(), + _ => "Unknown".to_string(), + }; + stats.output.umfpack_strategy = match self.effective_strategy { + UMFPACK_STRATEGY_AUTO => "Auto".to_string(), + UMFPACK_STRATEGY_UNSYMMETRIC => "Unsymmetric".to_string(), + UMFPACK_STRATEGY_SYMMETRIC => "Symmetric".to_string(), + _ => "Unknown".to_string(), + }; + stats.time_nanoseconds.initialize = self.time_initialize_ns; + stats.time_nanoseconds.factorize = self.time_factorize_ns; + stats.time_nanoseconds.solve = self.time_solve_ns; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ComplexCooMatrix, ComplexSparseMatrix, Ordering, Samples, Scaling, Storage}; + use num_complex::Complex64; + use russell_lab::{complex_approx_eq, complex_vec_approx_eq, cpx, ComplexVector}; + + #[test] + fn new_and_drop_work() { + // you may debug into the C-code to see that drop is working + let solver = ComplexSolverUMFPACK::new().unwrap(); + assert!(!solver.factorized); + } + + #[test] + fn factorize_handles_errors() { + let mut solver = ComplexSolverUMFPACK::new().unwrap(); + assert!(!solver.factorized); + + // COO to CSC errors + let coo = ComplexCooMatrix::new(1, 1, 1, None).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("COO to CSC requires nnz > 0") + ); + + // check CSC matrix + let (coo, _, _, _) = Samples::complex_rectangular_4x3(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("the matrix must be square") + ); + let (coo, _, _, _) = Samples::complex_symmetric_3x3_lower(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("for UMFPACK, the matrix must not be triangular") + ); + + // check already factorized data + let mut coo = ComplexCooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + coo.put(1, 1, cpx!(2.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + // ... factorize once => OK + solver.factorize(&mut mat, None).unwrap(); + // ... change matrix (symmetry) + let mut coo = ComplexCooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + coo.put(1, 1, cpx!(2.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("subsequent factorizations must use the same matrix (symmetry differs)") + ); + // ... change matrix (ndim) + let mut coo = ComplexCooMatrix::new(1, 1, 1, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("subsequent factorizations must use the same matrix (ndim differs)") + ); + // ... change matrix (nnz) + let mut coo = ComplexCooMatrix::new(2, 2, 1, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!( + solver.factorize(&mut mat, None).err(), + Some("subsequent factorizations must use the same matrix (nnz differs)") + ); + } + + #[test] + fn factorize_works() { + let mut solver = ComplexSolverUMFPACK::new().unwrap(); + assert!(!solver.factorized); + let (coo, _, _, _) = Samples::complex_symmetric_3x3_full(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut params = LinSolParams::new(); + + params.compute_determinant = true; + params.ordering = Ordering::Amd; + params.scaling = Scaling::Sum; + + solver.factorize(&mut mat, Some(params)).unwrap(); + assert!(solver.factorized); + + assert_eq!(solver.effective_ordering, UMFPACK_ORDERING_AMD); + assert_eq!(solver.effective_scaling, UMFPACK_SCALE_SUM); + + let m = cpx!(solver.determinant_coefficient_real, solver.determinant_coefficient_imag); + let det = m * f64::powf(10.0, solver.determinant_exponent); + complex_approx_eq(det, cpx!(6.0, 10.0), 1e-14); + + // calling factorize again works + solver.factorize(&mut mat, Some(params)).unwrap(); + let m = cpx!(solver.determinant_coefficient_real, solver.determinant_coefficient_imag); + let det = m * f64::powf(10.0, solver.determinant_exponent); + complex_approx_eq(det, cpx!(6.0, 10.0), 1e-14); + } + + #[test] + fn factorize_fails_on_singular_matrix() { + let mut solver = ComplexSolverUMFPACK::new().unwrap(); + let mut coo = ComplexCooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, cpx!(1.0, 0.0)).unwrap(); + coo.put(1, 1, cpx!(0.0, 0.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + assert_eq!(solver.factorize(&mut mat, None), Err("Error(1): Matrix is singular")); + } + + #[test] + fn solve_handles_errors() { + let mut coo = ComplexCooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + coo.put(1, 1, cpx!(456.0, 2.0)).unwrap(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut solver = ComplexSolverUMFPACK::new().unwrap(); + assert!(!solver.factorized); + let mut x = ComplexVector::new(2); + let rhs = ComplexVector::new(2); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the function factorize must be called before solve") + ); + let mut x = ComplexVector::new(1); + solver.factorize(&mut mat, None).unwrap(); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the dimension of the vector of unknown values x is incorrect") + ); + let mut x = ComplexVector::new(2); + let rhs = ComplexVector::new(1); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the dimension of the right-hand side vector is incorrect") + ); + // wrong symmetry + let rhs = ComplexVector::new(2); + let mut coo_wrong = ComplexCooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo_wrong.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + coo_wrong.put(1, 1, cpx!(456.0, 2.0)).unwrap(); + let mut mat_wrong = ComplexSparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (symmetry differs)") + ); + // wrong ndim + let mut coo_wrong = ComplexCooMatrix::new(1, 1, 1, None).unwrap(); + coo_wrong.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + let mut mat_wrong = ComplexSparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (ndim differs)") + ); + // wrong nnz + let mut coo_wrong = ComplexCooMatrix::new(2, 2, 3, None).unwrap(); + coo_wrong.put(0, 0, cpx!(123.0, 1.0)).unwrap(); + coo_wrong.put(1, 1, cpx!(456.0, 2.0)).unwrap(); + coo_wrong.put(0, 1, cpx!(100.0, 1.0)).unwrap(); + let mut mat_wrong = ComplexSparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (nnz differs)") + ); + } + + #[test] + fn solve_works() { + let mut solver = ComplexSolverUMFPACK::new().unwrap(); + let (coo, _, _, _) = Samples::complex_symmetric_3x3_full(); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut x = ComplexVector::new(3); + let rhs = ComplexVector::from(&[cpx!(-3.0, 3.0), cpx!(2.0, -2.0), cpx!(9.0, 7.0)]); + let x_correct = &[cpx!(1.0, 1.0), cpx!(2.0, -2.0), cpx!(3.0, 3.0)]; + solver.factorize(&mut mat, None).unwrap(); + solver.solve(&mut x, &mut mat, &rhs, false).unwrap(); + complex_vec_approx_eq(x.as_data(), x_correct, 1e-14); + + // calling solve again works + let mut x_again = ComplexVector::new(3); + solver.solve(&mut x_again, &mut mat, &rhs, false).unwrap(); + complex_vec_approx_eq(x_again.as_data(), x_correct, 1e-14); + + // update stats + let mut stats = StatsLinSol::new(); + solver.update_stats(&mut stats); + assert_eq!(stats.output.effective_ordering, "Amd"); + assert_eq!(stats.output.effective_scaling, "Sum"); + } +} diff --git a/russell_sparse/src/coo_matrix.rs b/russell_sparse/src/coo_matrix.rs index 07078f5d..1fc6c702 100644 --- a/russell_sparse/src/coo_matrix.rs +++ b/russell_sparse/src/coo_matrix.rs @@ -1,7 +1,11 @@ use super::{Storage, Symmetry}; use crate::to_i32; use crate::StrError; -use russell_lab::{Matrix, Vector}; +use num_traits::{Num, NumCast}; +use russell_lab::{NumMatrix, NumVector}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::ops::{AddAssign, MulAssign}; /// Holds the row index, col index, and values of a matrix (also known as Triplet) /// @@ -13,13 +17,13 @@ use russell_lab::{Matrix, Vector}; /// * The repeated (i,j) capability is of great convenience for Finite Element solvers /// * A maximum number of entries must be decided prior to allocating a new COO matrix /// * The maximum number of entries includes possible entries with repeated indices -#[derive(Clone)] -pub struct CooMatrix { +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct NumCooMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ /// Defines the symmetry and storage: lower-triangular, upper-triangular, full-matrix - /// - /// **Note:** `None` means unsymmetric matrix or unspecified symmetry, - /// where the storage is automatically considered as `Full`. - pub(crate) symmetry: Option, + pub(crate) symmetry: Symmetry, /// Holds the number of rows (must fit i32) pub(crate) nrow: usize, @@ -64,16 +68,14 @@ pub struct CooMatrix { /// ```text /// values.len() = max_nnz /// ``` - pub(crate) values: Vec, - - /// Defines the use of one-based indexing instead of zero-based (default) - /// - /// This option applies to indices_i and indices_j and enables the use of - /// FORTRAN routines such as the ones implemented by the MUMPS solver. - pub(crate) one_based: bool, + #[serde(bound(deserialize = "Vec: Deserialize<'de>"))] + pub(crate) values: Vec, } -impl CooMatrix { +impl NumCooMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ /// Creates a new COO matrix representing a sparse matrix /// /// # Input @@ -83,7 +85,6 @@ impl CooMatrix { /// * `max_nnz` -- (≥ 1) Maximum number of entries ≥ nnz (number of non-zeros), /// including entries with repeated indices. (must be fit i32) /// * `symmetry` -- Defines the symmetry/storage, if any - /// * `one_based` -- Use one-based indices; e.g., for MUMPS or other FORTRAN routines /// /// # Examples /// @@ -98,7 +99,7 @@ impl CooMatrix { /// // . -1 -3 2 . /// // . . 1 . . /// // . 4 2 . 1 - /// let mut coo = CooMatrix::new(5, 5, 13, None, true)?; + /// let mut coo = CooMatrix::new(5, 5, 13, None)?; /// coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate /// coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate /// coo.put(1, 0, 3.0)?; @@ -166,13 +167,7 @@ impl CooMatrix { /// Ok(()) /// } /// ``` - pub fn new( - nrow: usize, - ncol: usize, - max_nnz: usize, - symmetry: Option, - one_based: bool, - ) -> Result { + pub fn new(nrow: usize, ncol: usize, max_nnz: usize, symmetry: Option) -> Result { if nrow < 1 { return Err("nrow must be ≥ 1"); } @@ -182,16 +177,15 @@ impl CooMatrix { if max_nnz < 1 { return Err("max_nnz must be ≥ 1"); } - Ok(CooMatrix { - symmetry, + Ok(NumCooMatrix { + symmetry: if let Some(v) = symmetry { v } else { Symmetry::No }, nrow, ncol, nnz: 0, max_nnz, indices_i: vec![0; max_nnz], indices_j: vec![0; max_nnz], - values: vec![0.0; max_nnz], - one_based, + values: vec![T::zero(); max_nnz], }) } @@ -205,7 +199,6 @@ impl CooMatrix { /// * `col_indices` -- (len = nnz) Is the array of columns indices /// * `values` -- (len = nnz) Is the array of non-zero values /// * `symmetry` -- Defines the symmetry/storage, if any - /// * `one_based` -- Use one-based indices; e.g., for MUMPS or other FORTRAN routines /// /// # Examples /// @@ -228,7 +221,7 @@ impl CooMatrix { /// 1.0, /*dup*/ 1.0, 3.0, 3.0, -1.0, 4.0, 4.0, -3.0, 1.0, 2.0, 2.0, 6.0, 1.0, /// ]; /// let symmetry = None; - /// let coo = CooMatrix::from(nrow, ncol, row_indices, col_indices, values, symmetry, false)?; + /// let coo = CooMatrix::from(nrow, ncol, row_indices, col_indices, values, symmetry)?; /// /// // covert to dense /// let a = coo.as_dense(); @@ -248,9 +241,8 @@ impl CooMatrix { ncol: usize, row_indices: Vec, col_indices: Vec, - values: Vec, + values: Vec, symmetry: Option, - one_based: bool, ) -> Result { if nrow < 1 { return Err("nrow must be ≥ 1"); @@ -268,19 +260,18 @@ impl CooMatrix { if values.len() != nnz { return Err("values.len() must be = nnz"); } - let d = if one_based { 1 } else { 0 }; let m = to_i32(nrow); let n = to_i32(ncol); for k in 0..nnz { - if row_indices[k] - d < 0 || row_indices[k] - d >= m { + if row_indices[k] < 0 || row_indices[k] >= m { return Err("row index is out-of-range"); } - if col_indices[k] - d < 0 || col_indices[k] - d >= n { + if col_indices[k] < 0 || col_indices[k] >= n { return Err("col index is out-of-range"); } } - Ok(CooMatrix { - symmetry, + Ok(NumCooMatrix { + symmetry: if let Some(v) = symmetry { v } else { Symmetry::No }, nrow, ncol, nnz, @@ -288,7 +279,6 @@ impl CooMatrix { indices_i: row_indices, indices_j: col_indices, values, - one_based, }) } @@ -309,7 +299,7 @@ impl CooMatrix { /// /// fn main() -> Result<(), StrError> { /// let (nrow, ncol, nnz) = (3, 3, 4); - /// let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, nnz, None)?; /// coo.put(0, 0, 1.0)?; /// coo.put(1, 1, 2.0)?; /// coo.put(2, 2, 3.0)?; @@ -324,7 +314,7 @@ impl CooMatrix { /// Ok(()) /// } /// ``` - pub fn put(&mut self, i: usize, j: usize, aij: f64) -> Result<(), StrError> { + pub fn put(&mut self, i: usize, j: usize, aij: T) -> Result<(), StrError> { // check range if i >= self.nrow { return Err("COO matrix: index of row is outside range"); @@ -335,12 +325,12 @@ impl CooMatrix { if self.nnz >= self.max_nnz { return Err("COO matrix: max number of items has been reached"); } - if let Some(sym) = self.symmetry { - if sym.lower() { + if self.symmetry != Symmetry::No { + if self.symmetry.lower() { if j > i { return Err("COO matrix: j > i is incorrect for lower triangular storage"); } - } else if sym.upper() { + } else if self.symmetry.upper() { if j < i { return Err("COO matrix: j < i is incorrect for upper triangular storage"); } @@ -350,9 +340,8 @@ impl CooMatrix { // insert a new entry let i_i32 = to_i32(i); let j_i32 = to_i32(j); - let d = if self.one_based { 1 } else { 0 }; - self.indices_i[self.nnz] = i_i32 + d; - self.indices_j[self.nnz] = j_i32 + d; + self.indices_i[self.nnz] = i_i32; + self.indices_j[self.nnz] = j_i32; self.values[self.nnz] = aij; self.nnz += 1; Ok(()) @@ -370,7 +359,7 @@ impl CooMatrix { /// /// fn main() -> Result<(), StrError> { /// let (nrow, ncol, max_nnz) = (3, 3, 10); - /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None)?; /// coo.put(0, 0, 1.0)?; /// coo.put(1, 1, 2.0)?; /// coo.put(2, 2, 3.0)?; @@ -386,7 +375,7 @@ impl CooMatrix { /// assert_eq!(nrow, 3); /// assert_eq!(ncol, 3); /// assert_eq!(nnz, 0); - /// assert_eq!(symmetry, None); + /// assert_eq!(symmetry, Symmetry::No); /// Ok(()) /// } /// ``` @@ -404,7 +393,7 @@ impl CooMatrix { /// // define (4 x 4) sparse matrix with 6+1 non-zero values /// // (with an extra ij-repeated entry) /// let (nrow, ncol, max_nnz) = (4, 4, 10); - /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None)?; /// coo.put(0, 0, 0.5)?; // (0, 0, a00/2) << duplicate /// coo.put(0, 0, 0.5)?; // (0, 0, a00/2) << duplicate /// coo.put(0, 1, 2.0)?; @@ -425,8 +414,8 @@ impl CooMatrix { /// Ok(()) /// } /// ``` - pub fn as_dense(&self) -> Matrix { - let mut a = Matrix::new(self.nrow, self.ncol); + pub fn as_dense(&self) -> NumMatrix { + let mut a = NumMatrix::new(self.nrow, self.ncol); self.to_dense(&mut a).unwrap(); a } @@ -448,7 +437,7 @@ impl CooMatrix { /// // define (4 x 4) sparse matrix with 6+1 non-zero values /// // (with an extra ij-repeated entry) /// let (nrow, ncol, max_nnz) = (4, 4, 10); - /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None)?; /// coo.put(0, 0, 0.5)?; // (0, 0, a00/2) << duplicate /// coo.put(0, 0, 0.5)?; // (0, 0, a00/2) << duplicate /// coo.put(0, 1, 2.0)?; @@ -470,20 +459,16 @@ impl CooMatrix { /// Ok(()) /// } /// ``` - pub fn to_dense(&self, a: &mut Matrix) -> Result<(), StrError> { + pub fn to_dense(&self, a: &mut NumMatrix) -> Result<(), StrError> { let (m, n) = a.dims(); if m != self.nrow || n != self.ncol { return Err("wrong matrix dimensions"); } - let mirror_required = match self.symmetry { - Some(sym) => sym.triangular(), - None => false, - }; - a.fill(0.0); - let d = if self.one_based { 1 } else { 0 }; + let mirror_required = self.symmetry.triangular(); + a.fill(T::zero()); for p in 0..self.nnz { - let i = (self.indices_i[p] - d) as usize; - let j = (self.indices_j[p] - d) as usize; + let i = self.indices_i[p] as usize; + let j = self.indices_j[p] as usize; a.add(i, j, self.values[p]); if mirror_required && i != j { a.add(j, i, self.values[p]); @@ -521,7 +506,7 @@ impl CooMatrix { /// fn main() -> Result<(), StrError> { /// // set sparse matrix (3 x 3) with 6 non-zeros /// let (nrow, ncol, max_nnz) = (3, 3, 6); - /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, max_nnz, None)?; /// coo.put(0, 0, 1.0)?; /// coo.put(1, 0, 2.0)?; /// coo.put(1, 1, 3.0)?; @@ -553,31 +538,82 @@ impl CooMatrix { /// Ok(()) /// } /// ``` - pub fn mat_vec_mul(&self, v: &mut Vector, alpha: f64, u: &Vector) -> Result<(), StrError> { + pub fn mat_vec_mul(&self, v: &mut NumVector, alpha: T, u: &NumVector) -> Result<(), StrError> { if u.dim() != self.ncol { - return Err("u.ndim must equal ncol"); + return Err("u vector is incompatible"); } if v.dim() != self.nrow { - return Err("v.ndim must equal nrow"); + return Err("v vector is incompatible"); } - let mirror_required = match self.symmetry { - Some(sym) => sym.triangular(), - None => false, - }; - v.fill(0.0); - let d = if self.one_based { 1 } else { 0 }; + let mirror_required = self.symmetry.triangular(); + v.fill(T::zero()); for p in 0..self.nnz { - let i = (self.indices_i[p] - d) as usize; - let j = (self.indices_j[p] - d) as usize; + let i = self.indices_i[p] as usize; + let j = self.indices_j[p] as usize; let aij = self.values[p]; v[i] += alpha * aij * u[j]; if mirror_required && i != j { - v[j] += aij * u[i]; + v[j] += alpha * aij * u[i]; } } Ok(()) } + /// Assigns this matrix to the values of another matrix (scaled) + /// + /// Performs: + /// + /// ```text + /// this = α · other + /// ``` + /// + /// **Warning:** make sure to allocate `max_nnz ≥ nnz(other)`. + pub fn assign(&mut self, alpha: T, other: &NumCooMatrix) -> Result<(), StrError> { + if other.nrow != self.nrow { + return Err("matrices must have the same nrow"); + } + if other.ncol != self.ncol { + return Err("matrices must have the same ncol"); + } + if other.symmetry != self.symmetry { + return Err("matrices must have the same symmetry"); + } + self.reset(); + for p in 0..other.nnz { + let i = other.indices_i[p] as usize; + let j = other.indices_j[p] as usize; + self.put(i, j, alpha * other.values[p])?; + } + Ok(()) + } + + /// Augments this matrix with the entries of another matrix (scaled) + /// + /// Effectively, performs: + /// + /// ```text + /// this += α · other + /// ``` + /// + /// **Warning:** make sure to allocate `max_nnz ≥ nnz(this) + nnz(other)`. + pub fn augment(&mut self, alpha: T, other: &NumCooMatrix) -> Result<(), StrError> { + if other.nrow != self.nrow { + return Err("matrices must have the same nrow"); + } + if other.ncol != self.ncol { + return Err("matrices must have the same ncol"); + } + if other.symmetry != self.symmetry { + return Err("matrices must have the same symmetry"); + } + for p in 0..other.nnz { + let i = other.indices_i[p] as usize; + let j = other.indices_j[p] as usize; + self.put(i, j, alpha * other.values[p])?; + } + Ok(()) + } + /// Returns information about the dimensions and symmetry type /// /// Returns `(nrow, ncol, nnz, symmetry)` @@ -589,16 +625,16 @@ impl CooMatrix { /// use russell_sparse::StrError; /// /// fn main() -> Result<(), StrError> { - /// let coo = CooMatrix::new(1, 2, 3, None, false)?; + /// let coo = CooMatrix::new(1, 2, 3, None)?; /// let (nrow, ncol, nnz, symmetry) = coo.get_info(); /// assert_eq!(nrow, 1); /// assert_eq!(ncol, 2); /// assert_eq!(nnz, 0); - /// assert_eq!(symmetry, None); + /// assert_eq!(symmetry, Symmetry::No); /// Ok(()) /// } /// ``` - pub fn get_info(&self) -> (usize, usize, usize, Option) { + pub fn get_info(&self) -> (usize, usize, usize, Symmetry) { (self.nrow, self.ncol, self.nnz, self.symmetry) } @@ -609,30 +645,45 @@ impl CooMatrix { /// Returns whether the symmetry flag corresponds to a symmetric matrix or not pub fn get_symmetric(&self) -> bool { - match self.symmetry { - Some(_) => true, - None => false, - } + self.symmetry != Symmetry::No } /// Get an access to the row indices + /// + /// ```text + /// row_indices.len() == nnz + /// ``` pub fn get_row_indices(&self) -> &[i32] { - &self.indices_i + &self.indices_i[..self.nnz] } /// Get an access to the column indices + /// + /// ```text + /// col_indices.len() == nnz + /// ``` pub fn get_col_indices(&self) -> &[i32] { - &self.indices_j + &self.indices_j[..self.nnz] } /// Get an access to the values - pub fn get_values(&self) -> &[f64] { - &self.values + /// + /// ```text + /// values.len() == nnz + /// ``` + pub fn get_values(&self) -> &[T] { + &self.values[..self.nnz] } /// Get a mutable access the values - pub fn get_values_mut(&mut self) -> &mut [f64] { - &mut self.values + /// + /// ```text + /// values.len() == nnz + /// ``` + /// + /// Note: the values may be modified externally, but not the indices. + pub fn get_values_mut(&mut self) -> &mut [T] { + &mut self.values[..self.nnz] } } @@ -640,21 +691,25 @@ impl CooMatrix { #[cfg(test)] mod tests { - use super::CooMatrix; + use super::NumCooMatrix; use crate::{Samples, Storage, Symmetry}; - use russell_lab::{vec_approx_eq, Matrix, Vector}; + use num_complex::Complex64; + use russell_lab::{complex_vec_approx_eq, cpx, vec_approx_eq, ComplexVector, NumMatrix, NumVector}; #[test] fn new_captures_errors() { - assert_eq!(CooMatrix::new(0, 1, 3, None, false).err(), Some("nrow must be ≥ 1")); - assert_eq!(CooMatrix::new(1, 0, 3, None, false).err(), Some("ncol must be ≥ 1")); - assert_eq!(CooMatrix::new(1, 1, 0, None, false).err(), Some("max_nnz must be ≥ 1")); + assert_eq!(NumCooMatrix::::new(0, 1, 3, None).err(), Some("nrow must be ≥ 1")); + assert_eq!(NumCooMatrix::::new(1, 0, 3, None).err(), Some("ncol must be ≥ 1")); + assert_eq!( + NumCooMatrix::::new(1, 1, 0, None).err(), + Some("max_nnz must be ≥ 1") + ); } #[test] fn new_works() { - let coo = CooMatrix::new(1, 1, 3, None, false).unwrap(); - assert_eq!(coo.symmetry, None); + let coo = NumCooMatrix::::new(1, 1, 3, None).unwrap(); + assert_eq!(coo.symmetry, Symmetry::No); assert_eq!(coo.nrow, 1); assert_eq!(coo.ncol, 1); assert_eq!(coo.nnz, 0); @@ -667,77 +722,76 @@ mod tests { #[test] #[rustfmt::skip] fn from_captures_errors(){ - assert_eq!(CooMatrix::from(0, 1, vec![ 0], vec![ 0], vec![0.0], None, false).err(), Some("nrow must be ≥ 1")); - assert_eq!(CooMatrix::from(1, 0, vec![ 0], vec![ 0], vec![0.0], None, false).err(), Some("ncol must be ≥ 1")); - assert_eq!(CooMatrix::from(1, 1, vec![ ], vec![ 0], vec![0.0], None, false).err(), Some("nnz must be ≥ 1")); - assert_eq!(CooMatrix::from(1, 1, vec![ 0], vec![ ], vec![0.0], None, false).err(), Some("col_indices.len() must be = nnz")); - assert_eq!(CooMatrix::from(1, 1, vec![ 0], vec![ 0], vec![ ], None, false).err(), Some("values.len() must be = nnz")); - assert_eq!(CooMatrix::from(1, 1, vec![-1], vec![ 0], vec![0.0], None, false).err(), Some("row index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 1], vec![ 0], vec![0.0], None, false).err(), Some("row index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 0], vec![-1], vec![0.0], None, false).err(), Some("col index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 0], vec![ 1], vec![0.0], None, false).err(), Some("col index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 0], vec![ 1], vec![0.0], None, true).err(), Some("row index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 2], vec![ 1], vec![0.0], None, true).err(), Some("row index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 1], vec![ 0], vec![0.0], None, true).err(), Some("col index is out-of-range")); - assert_eq!(CooMatrix::from(1, 1, vec![ 1], vec![ 2], vec![0.0], None, true).err(), Some("col index is out-of-range")); + assert_eq!(NumCooMatrix::::from(0, 1, vec![ 0], vec![ 0], vec![0.0], None).err(), Some("nrow must be ≥ 1")); + assert_eq!(NumCooMatrix::::from(1, 0, vec![ 0], vec![ 0], vec![0.0], None).err(), Some("ncol must be ≥ 1")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![ ], vec![ 0], vec![0.0], None).err(), Some("nnz must be ≥ 1")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![ 0], vec![ ], vec![0.0], None).err(), Some("col_indices.len() must be = nnz")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![ 0], vec![ 0], vec![ ], None).err(), Some("values.len() must be = nnz")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![-1], vec![ 0], vec![0.0], None).err(), Some("row index is out-of-range")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![ 1], vec![ 0], vec![0.0], None).err(), Some("row index is out-of-range")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![ 0], vec![-1], vec![0.0], None).err(), Some("col index is out-of-range")); + assert_eq!(NumCooMatrix::::from(1, 1, vec![ 0], vec![ 1], vec![0.0], None).err(), Some("col index is out-of-range")); } #[test] fn from_works() { - let coo = CooMatrix::from(1, 1, vec![1], vec![1], vec![123.0], None, true).unwrap(); - assert_eq!(coo.symmetry, None); + let coo = NumCooMatrix::::from(1, 1, vec![0], vec![0], vec![123.0], None).unwrap(); + assert_eq!(coo.symmetry, Symmetry::No); assert_eq!(coo.nrow, 1); assert_eq!(coo.ncol, 1); assert_eq!(coo.nnz, 1); assert_eq!(coo.max_nnz, 1); - assert_eq!(coo.indices_i, &[1]); - assert_eq!(coo.indices_j, &[1]); + assert_eq!(coo.indices_i, &[0]); + assert_eq!(coo.indices_j, &[0]); assert_eq!(coo.values, &[123.0]); + let sym = Some(Symmetry::new_general_full()); + let coo = NumCooMatrix::::from(1, 1, vec![0], vec![0], vec![123.0], sym).unwrap(); + assert_eq!(coo.symmetry, Symmetry::General(Storage::Full)); } #[test] fn get_info_works() { - let coo = CooMatrix::new(1, 2, 10, None, false).unwrap(); + let coo = NumCooMatrix::::new(1, 2, 10, None).unwrap(); let (nrow, ncol, nnz, symmetry) = coo.get_info(); assert_eq!(nrow, 1); assert_eq!(ncol, 2); assert_eq!(nnz, 0); - assert_eq!(symmetry, None); + assert_eq!(symmetry, Symmetry::No); } #[test] fn put_fails_on_wrong_values() { - let mut coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(1, 1, 1, None).unwrap(); assert_eq!( - coo.put(1, 0, 0.0).err(), + coo.put(1, 0, 0).err(), Some("COO matrix: index of row is outside range") ); assert_eq!( - coo.put(0, 1, 0.0).err(), + coo.put(0, 1, 0).err(), Some("COO matrix: index of column is outside range") ); - assert_eq!(coo.put(0, 0, 0.0).err(), None); // << will take all spots + assert_eq!(coo.put(0, 0, 0).err(), None); // << will take all spots assert_eq!( - coo.put(0, 0, 0.0).err(), + coo.put(0, 0, 0).err(), Some("COO matrix: max number of items has been reached") ); let sym = Some(Symmetry::General(Storage::Lower)); - let mut coo = CooMatrix::new(2, 2, 4, sym, false).unwrap(); + let mut coo = NumCooMatrix::::new(2, 2, 4, sym).unwrap(); assert_eq!( - coo.put(0, 1, 0.0).err(), + coo.put(0, 1, 0).err(), Some("COO matrix: j > i is incorrect for lower triangular storage") ); let sym = Some(Symmetry::General(Storage::Upper)); - let mut coo = CooMatrix::new(2, 2, 4, sym, false).unwrap(); + let mut coo = NumCooMatrix::::new(2, 2, 4, sym).unwrap(); assert_eq!( - coo.put(1, 0, 0.0).err(), + coo.put(1, 0, 0).err(), Some("COO matrix: j < i is incorrect for upper triangular storage") ); } #[test] fn put_works() { - let mut coo = CooMatrix::new(3, 3, 5, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(3, 3, 5, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); assert_eq!(coo.nnz, 1); coo.put(0, 1, 2.0).unwrap(); @@ -752,7 +806,7 @@ mod tests { #[test] fn reset_works() { - let mut coo = CooMatrix::new(2, 2, 4, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(2, 2, 4, None).unwrap(); assert_eq!(coo.nnz, 0); coo.put(0, 0, 1.0).unwrap(); coo.put(0, 1, 4.0).unwrap(); @@ -765,23 +819,23 @@ mod tests { #[test] fn to_dense_fails_on_wrong_dims() { - let mut coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(1, 1, 1, None).unwrap(); coo.put(0, 0, 123.0).unwrap(); - let mut a_2x1 = Matrix::new(2, 1); - let mut a_1x2 = Matrix::new(1, 2); + let mut a_2x1 = NumMatrix::::new(2, 1); + let mut a_1x2 = NumMatrix::::new(1, 2); assert_eq!(coo.to_dense(&mut a_2x1), Err("wrong matrix dimensions")); assert_eq!(coo.to_dense(&mut a_1x2), Err("wrong matrix dimensions")); } #[test] fn to_dense_works() { - let mut coo = CooMatrix::new(3, 3, 5, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(3, 3, 5, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(0, 1, 2.0).unwrap(); coo.put(1, 0, 3.0).unwrap(); coo.put(1, 1, 4.0).unwrap(); coo.put(2, 2, 5.0).unwrap(); - let mut a = Matrix::new(3, 3); + let mut a = NumMatrix::::new(3, 3); coo.to_dense(&mut a).unwrap(); assert_eq!(a.get(0, 0), 1.0); assert_eq!(a.get(0, 1), 2.0); @@ -800,11 +854,11 @@ mod tests { assert_eq!(bb.get(0, 0), 1.0); assert_eq!(bb.get(1, 0), 3.0); // empty matrix - let empty = CooMatrix::new(2, 2, 3, None, false).unwrap(); + let empty = NumCooMatrix::::new(2, 2, 3, None).unwrap(); let mat = empty.as_dense(); assert_eq!(mat.as_data(), &[0.0, 0.0, 0.0, 0.0]); // single component matrix - let mut single = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let mut single = NumCooMatrix::::new(1, 1, 1, None).unwrap(); single.put(0, 0, 123.0).unwrap(); let mat = single.as_dense(); assert_eq!(mat.as_data(), &[123.0]); @@ -814,7 +868,7 @@ mod tests { fn to_dense_with_duplicates_works() { // allocate a square matrix let (nrow, ncol, nnz) = (5, 5, 13); - let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(nrow, ncol, nnz, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) coo.put(1, 0, 3.0).unwrap(); @@ -830,7 +884,7 @@ mod tests { coo.put(4, 4, 1.0).unwrap(); // print matrix - let mut a = Matrix::new(nrow as usize, ncol as usize); + let mut a = NumMatrix::::new(nrow as usize, ncol as usize); coo.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ │ 2 3 0 0 0 │\n\ @@ -845,12 +899,12 @@ mod tests { #[test] fn to_dense_symmetric_lower_works() { let sym = Some(Symmetry::General(Storage::Lower)); - let mut coo = CooMatrix::new(3, 3, 4, sym, false).unwrap(); + let mut coo = NumCooMatrix::::new(3, 3, 4, sym).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 0, 2.0).unwrap(); coo.put(1, 1, 3.0).unwrap(); coo.put(2, 1, 4.0).unwrap(); - let mut a = Matrix::new(3, 3); + let mut a = NumMatrix::::new(3, 3); coo.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ │ 1 2 0 │\n\ @@ -861,14 +915,14 @@ mod tests { } #[test] - fn to_dense_symmetric_upper_and_one_based_works() { + fn to_dense_symmetric_upper_works() { let sym = Some(Symmetry::General(Storage::Upper)); - let mut coo = CooMatrix::new(3, 3, 4, sym, true).unwrap(); + let mut coo = NumCooMatrix::::new(3, 3, 4, sym).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(0, 1, 2.0).unwrap(); coo.put(1, 1, 3.0).unwrap(); coo.put(1, 2, 4.0).unwrap(); - let mut a = Matrix::new(3, 3); + let mut a = NumMatrix::::new(3, 3); coo.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ │ 1 2 0 │\n\ @@ -879,15 +933,15 @@ mod tests { } #[test] - fn mat_vec_mul_fails_on_wrong_input() { - let mut coo = CooMatrix::new(2, 2, 1, None, false).unwrap(); - coo.put(0, 0, 123.0).unwrap(); - let u = Vector::new(3); - let mut v = Vector::new(coo.nrow); - assert_eq!(coo.mat_vec_mul(&mut v, 1.0, &u).err(), Some("u.ndim must equal ncol")); - let u = Vector::new(2); - let mut v = Vector::new(1); - assert_eq!(coo.mat_vec_mul(&mut v, 1.0, &u).err(), Some("v.ndim must equal nrow")); + fn mat_vec_mul_captures_errors() { + let mut coo = NumCooMatrix::::new(2, 2, 1, None).unwrap(); + coo.put(0, 0, 123).unwrap(); + let u = NumVector::::new(3); + let mut v = NumVector::::new(coo.nrow); + assert_eq!(coo.mat_vec_mul(&mut v, 1, &u).err(), Some("u vector is incompatible")); + let u = NumVector::::new(2); + let mut v = NumVector::::new(1); + assert_eq!(coo.mat_vec_mul(&mut v, 1, &u).err(), Some("v vector is incompatible")); } #[test] @@ -895,7 +949,7 @@ mod tests { // 1.0 2.0 3.0 // 0.1 0.2 0.3 // 10.0 20.0 30.0 - let mut coo = CooMatrix::new(3, 3, 9, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(3, 3, 9, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(0, 1, 2.0).unwrap(); coo.put(0, 2, 3.0).unwrap(); @@ -905,38 +959,23 @@ mod tests { coo.put(2, 0, 10.0).unwrap(); coo.put(2, 1, 20.0).unwrap(); coo.put(2, 2, 30.0).unwrap(); - let u = Vector::from(&[0.1, 0.2, 0.3]); - let mut v = Vector::new(coo.nrow); - coo.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct_v = &[1.4, 0.14, 14.0]; + let u = NumVector::::from(&[0.1, 0.2, 0.3]); + let mut v = NumVector::::new(coo.nrow); + coo.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + let correct_v = &[2.8, 0.28, 28.0]; vec_approx_eq(v.as_data(), correct_v, 1e-15); // call mat_vec_mul again to make sure the vector is filled with zeros before the sum - coo.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - vec_approx_eq(v.as_data(), correct_v, 1e-15); - - // one-based indexing - let mut coo = CooMatrix::new(3, 3, 9, None, true).unwrap(); - coo.put(0, 0, 1.0).unwrap(); - coo.put(0, 1, 2.0).unwrap(); - coo.put(0, 2, 3.0).unwrap(); - coo.put(1, 0, 0.1).unwrap(); - coo.put(1, 1, 0.2).unwrap(); - coo.put(1, 2, 0.3).unwrap(); - coo.put(2, 0, 10.0).unwrap(); - coo.put(2, 1, 20.0).unwrap(); - coo.put(2, 2, 30.0).unwrap(); - coo.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct_v = &[1.4, 0.14, 14.0]; + coo.mat_vec_mul(&mut v, 2.0, &u).unwrap(); vec_approx_eq(v.as_data(), correct_v, 1e-15); // single component matrix - let mut single = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let mut single = NumCooMatrix::::new(1, 1, 1, None).unwrap(); single.put(0, 0, 123.0).unwrap(); - let u = Vector::from(&[2.0]); - let mut v = Vector::new(1); - single.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - assert_eq!(v.as_data(), &[246.0]); + let u = NumVector::from(&[2.0]); + let mut v = NumVector::::new(1); + single.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + assert_eq!(v.as_data(), &[492.0]); } #[test] @@ -948,7 +987,7 @@ mod tests { // 2 1 5 1 8 let (nrow, ncol, nnz) = (5, 5, 15); let sym = Some(Symmetry::General(Storage::Lower)); - let mut coo = CooMatrix::new(nrow, ncol, nnz, sym, false).unwrap(); + let mut coo = NumCooMatrix::::new(nrow, ncol, nnz, sym).unwrap(); coo.put(0, 0, 2.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); coo.put(2, 2, 9.0).unwrap(); @@ -968,10 +1007,10 @@ mod tests { coo.put(4, 1, 1.0).unwrap(); coo.put(4, 2, 5.0).unwrap(); coo.put(4, 3, 1.0).unwrap(); - let u = Vector::from(&[-629.0 / 98.0, 237.0 / 49.0, -53.0 / 49.0, 62.0 / 49.0, 23.0 / 14.0]); - let mut v = Vector::new(coo.nrow); - coo.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct_v = &[-2.0, 4.0, 3.0, -5.0, 1.0]; + let u = NumVector::::from(&[-629.0 / 98.0, 237.0 / 49.0, -53.0 / 49.0, 62.0 / 49.0, 23.0 / 14.0]); + let mut v = NumVector::::new(coo.nrow); + coo.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + let correct_v = &[-4.0, 8.0, 6.0, -10.0, 2.0]; vec_approx_eq(v.as_data(), correct_v, 1e-14); } @@ -984,7 +1023,7 @@ mod tests { // 2 1 5 1 8 let (nrow, ncol, nnz) = (5, 5, 25); let sym = Some(Symmetry::General(Storage::Full)); - let mut coo = CooMatrix::new(nrow, ncol, nnz, sym, false).unwrap(); + let mut coo = NumCooMatrix::::new(nrow, ncol, nnz, sym).unwrap(); coo.put(0, 0, 2.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); coo.put(2, 2, 9.0).unwrap(); @@ -1014,10 +1053,10 @@ mod tests { coo.put(2, 4, 5.0).unwrap(); coo.put(4, 3, 1.0).unwrap(); coo.put(3, 4, 1.0).unwrap(); - let u = Vector::from(&[-629.0 / 98.0, 237.0 / 49.0, -53.0 / 49.0, 62.0 / 49.0, 23.0 / 14.0]); - let mut v = Vector::new(coo.nrow); - coo.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct_v = &[-2.0, 4.0, 3.0, -5.0, 1.0]; + let u = NumVector::::from(&[-629.0 / 98.0, 237.0 / 49.0, -53.0 / 49.0, 62.0 / 49.0, 23.0 / 14.0]); + let mut v = NumVector::::new(coo.nrow); + coo.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + let correct_v = &[-4.0, 8.0, 6.0, -10.0, 2.0]; vec_approx_eq(v.as_data(), correct_v, 1e-14); } @@ -1028,33 +1067,160 @@ mod tests { // -1 2 -1 2 let (nrow, ncol, nnz) = (3, 3, 5); let sym = Some(Symmetry::PositiveDefinite(Storage::Lower)); - let mut coo = CooMatrix::new(nrow, ncol, nnz, sym, false).unwrap(); + let mut coo = NumCooMatrix::::new(nrow, ncol, nnz, sym).unwrap(); coo.put(0, 0, 2.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); coo.put(2, 2, 2.0).unwrap(); coo.put(1, 0, -1.0).unwrap(); coo.put(2, 1, -1.0).unwrap(); - let u = Vector::from(&[5.0, 8.0, 7.0]); - let mut v = Vector::new(coo.nrow); - coo.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct_v = &[2.0, 4.0, 6.0]; + let u = NumVector::::from(&[5.0, 8.0, 7.0]); + let mut v = NumVector::::new(coo.nrow); + coo.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + let correct_v = &[4.0, 8.0, 12.0]; vec_approx_eq(v.as_data(), correct_v, 1e-15); } + #[test] + fn mat_vec_mul_complex_works() { + // 4+4i . 2+2i + // . 1 3+3i + // . 5+5i 1+1i + // 1 . . + let (coo, _, _, _) = Samples::complex_rectangular_4x3(); + let u = ComplexVector::from(&[cpx!(1.0, 1.0), cpx!(3.0, 1.0), cpx!(5.0, -1.0)]); + let mut v = ComplexVector::new(coo.nrow); + coo.mat_vec_mul(&mut v, cpx!(2.0, 4.0), &u).unwrap(); + let correct = &[ + cpx!(-40.0, 80.0), + cpx!(-10.0, 110.0), + cpx!(-64.0, 112.0), + cpx!(-2.0, 6.0), + ]; + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + // call mat_vec_mul again to make sure the vector is filled with zeros before the sum + coo.mat_vec_mul(&mut v, cpx!(2.0, 4.0), &u).unwrap(); + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + } + + #[test] + fn assign_capture_errors() { + let sym = Some(Symmetry::General(Storage::Full)); + let nnz_a = 1; + let nnz_b = 2; // wrong: must be ≤ nnz_a + let mut a_1x2 = NumCooMatrix::::new(1, 2, nnz_a, None).unwrap(); + let b_2x1 = NumCooMatrix::::new(2, 1, nnz_b, None).unwrap(); + let b_1x3 = NumCooMatrix::::new(1, 3, nnz_b, None).unwrap(); + let b_1x2_sym = NumCooMatrix::::new(1, 2, nnz_b, sym).unwrap(); + let mut b_1x2 = NumCooMatrix::::new(1, 2, nnz_b, None).unwrap(); + a_1x2.put(0, 0, 123).unwrap(); + b_1x2.put(0, 0, 456).unwrap(); + b_1x2.put(0, 1, 654).unwrap(); + assert_eq!(a_1x2.assign(2, &b_2x1).err(), Some("matrices must have the same nrow")); + assert_eq!(a_1x2.assign(2, &b_1x3).err(), Some("matrices must have the same ncol")); + assert_eq!( + a_1x2.assign(2, &b_1x2_sym).err(), + Some("matrices must have the same symmetry") + ); + assert_eq!( + a_1x2.assign(2, &b_1x2).err(), + Some("COO matrix: max number of items has been reached") + ); + } + + #[test] + fn assign_works() { + let nnz = 2; + let mut a = NumCooMatrix::::new(3, 2, nnz, None).unwrap(); + let mut b = NumCooMatrix::::new(3, 2, nnz, None).unwrap(); + a.put(2, 1, 1000.0).unwrap(); + b.put(0, 0, 10.0).unwrap(); + b.put(2, 1, 20.0).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 0 0 │\n\ + │ 0 0 │\n\ + │ 0 1000 │\n\ + └ ┘" + ); + a.assign(5.0, &b).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 50 0 │\n\ + │ 0 0 │\n\ + │ 0 100 │\n\ + └ ┘" + ); + } + + #[test] + fn augment_capture_errors() { + let sym = Some(Symmetry::General(Storage::Full)); + let nnz_a = 1; + let nnz_b = 1; + let mut a_1x2 = NumCooMatrix::::new(1, 2, nnz_a /* + nnz_b */, None).unwrap(); + let b_2x1 = NumCooMatrix::::new(2, 1, nnz_b, None).unwrap(); + let b_1x3 = NumCooMatrix::::new(1, 3, nnz_b, None).unwrap(); + let b_1x2_sym = NumCooMatrix::::new(1, 2, nnz_b, sym).unwrap(); + let mut b_1x2 = NumCooMatrix::::new(1, 2, nnz_b, None).unwrap(); + a_1x2.put(0, 0, 123).unwrap(); + b_1x2.put(0, 0, 456).unwrap(); + assert_eq!(a_1x2.augment(2, &b_2x1).err(), Some("matrices must have the same nrow")); + assert_eq!(a_1x2.augment(2, &b_1x3).err(), Some("matrices must have the same ncol")); + assert_eq!( + a_1x2.augment(2, &b_1x2_sym).err(), + Some("matrices must have the same symmetry") + ); + assert_eq!( + a_1x2.augment(2, &b_1x2).err(), + Some("COO matrix: max number of items has been reached") + ); + } + + #[test] + fn augment_works() { + let nnz_a = 1; + let nnz_b = 2; + let mut a = NumCooMatrix::::new(3, 2, nnz_a + nnz_b, None).unwrap(); + let mut b = NumCooMatrix::::new(3, 2, nnz_b, None).unwrap(); + a.put(2, 1, 1000.0).unwrap(); + b.put(0, 0, 10.0).unwrap(); + b.put(2, 1, 20.0).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 0 0 │\n\ + │ 0 0 │\n\ + │ 0 1000 │\n\ + └ ┘" + ); + a.augment(5.0, &b).unwrap(); + assert_eq!( + format!("{}", a.as_dense()), + "┌ ┐\n\ + │ 50 0 │\n\ + │ 0 0 │\n\ + │ 0 1100 │\n\ + └ ┘" + ); + } + #[test] fn getters_are_correct() { - let (coo, _, _, _) = Samples::rectangular_1x2(false, false, false); - assert_eq!(coo.get_info(), (1, 2, 2, None)); + let (coo, _, _, _) = Samples::rectangular_1x2(false, false); + assert_eq!(coo.get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(coo.get_storage(), Storage::Full); assert_eq!(coo.get_symmetric(), false); - assert_eq!(coo.get_row_indices(), &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - assert_eq!(coo.get_col_indices(), &[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]); - assert_eq!(coo.get_values(), &[10.0, 20.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + assert_eq!(coo.get_row_indices(), &[0, 0]); + assert_eq!(coo.get_col_indices(), &[0, 1]); + assert_eq!(coo.get_values(), &[10.0, 20.0]); - let coo = CooMatrix::new(2, 2, 2, Symmetry::new_general_full(), false).unwrap(); + let sym = Some(Symmetry::new_general_full()); + let coo = NumCooMatrix::::new(2, 2, 2, sym).unwrap(); assert_eq!(coo.get_symmetric(), true); - let mut coo = CooMatrix::new(2, 1, 2, None, false).unwrap(); + let mut coo = NumCooMatrix::::new(2, 1, 2, None).unwrap(); coo.put(0, 0, 123.0).unwrap(); coo.put(1, 0, 456.0).unwrap(); assert_eq!(coo.get_values_mut(), &[123.0, 456.0]); @@ -1064,11 +1230,26 @@ mod tests { } #[test] - fn clone_works() { - let (coo, _, _, _) = Samples::tiny_1x1(false); + fn derive_methods_work() { + let (coo, _, _, _) = Samples::tiny_1x1(); let mut clone = coo.clone(); clone.values[0] *= 2.0; assert_eq!(coo.values[0], 123.0); assert_eq!(clone.values[0], 246.0); + assert!(format!("{:?}", coo).len() > 0); + let json = serde_json::to_string(&coo).unwrap(); + assert_eq!( + json, + r#"{"symmetry":"No","nrow":1,"ncol":1,"nnz":1,"max_nnz":1,"indices_i":[0],"indices_j":[0],"values":[123.0]}"# + ); + let from_json: NumCooMatrix = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json.symmetry, coo.symmetry); + assert_eq!(from_json.nrow, coo.nrow); + assert_eq!(from_json.ncol, coo.ncol); + assert_eq!(from_json.nnz, coo.nnz); + assert_eq!(from_json.max_nnz, coo.max_nnz); + assert_eq!(from_json.indices_i, coo.indices_i); + assert_eq!(from_json.indices_j, coo.indices_j); + assert_eq!(from_json.values, coo.values); } } diff --git a/russell_sparse/src/csc_matrix.rs b/russell_sparse/src/csc_matrix.rs index 34f35ef8..f2b1e849 100644 --- a/russell_sparse/src/csc_matrix.rs +++ b/russell_sparse/src/csc_matrix.rs @@ -1,25 +1,10 @@ -use super::{handle_umfpack_error_code, to_i32, CooMatrix, CsrMatrix, Symmetry}; +use super::{to_i32, NumCooMatrix, NumCsrMatrix, Symmetry}; use crate::StrError; -use russell_lab::{Matrix, Vector}; -use std::ffi::OsStr; -use std::fmt::Write; -use std::fs::{self, File}; -use std::io::Write as IoWrite; -use std::path::Path; - -extern "C" { - fn umfpack_coo_to_csc( - col_pointers: *mut i32, - row_indices: *mut i32, - values: *mut f64, - nrow: i32, - ncol: i32, - nnz: i32, - indices_i: *const i32, - indices_j: *const i32, - values_aij: *const f64, - ) -> i32; -} +use num_traits::{Num, NumCast}; +use russell_lab::{NumMatrix, NumVector}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::ops::{AddAssign, MulAssign}; /// Holds the arrays needed for a CSC (compressed sparse column) matrix /// @@ -62,13 +47,15 @@ extern "C" { /// ```text /// 0, 2, 5, 9, 10, 12 /// ``` -#[derive(Clone)] -pub struct CscMatrix { +/// +/// **Note:** The number of non-zero values is `nnz = col_pointers[ncol]` +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct NumCscMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ /// Defines the symmetry and storage: lower-triangular, upper-triangular, full-matrix - /// - /// **Note:** `None` means unsymmetric matrix or unspecified symmetry, - /// where the storage is automatically `Full`. - pub(crate) symmetry: Option, + pub(crate) symmetry: Symmetry, /// Holds the number of rows (must fit i32) pub(crate) nrow: usize, @@ -98,10 +85,34 @@ pub struct CscMatrix { /// nnz_dup ≥ nnz /// values.len() = nnz_dup /// ``` - pub(crate) values: Vec, + #[serde(bound(deserialize = "Vec: Deserialize<'de>"))] + pub(crate) values: Vec, + + /// Temporary row form (for COO to CSC conversion) + #[serde(skip)] + temp_rp: Vec, + + /// Temporary row form (for COO to CSC conversion) + #[serde(skip)] + temp_rj: Vec, + + /// Temporary row form (for COO to CSC conversion) + #[serde(skip)] + temp_rx: Vec, + + /// Temporary row count (for COO to CSC conversion) + #[serde(skip)] + temp_rc: Vec, + + /// Temporary workspace (for COO to CSC conversion) + #[serde(skip)] + temp_w: Vec, } -impl CscMatrix { +impl NumCscMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ /// Creates a new CSC matrix from data arrays /// /// **Note:** The column pointers and row indices must be **sorted** in ascending order. @@ -183,7 +194,7 @@ impl CscMatrix { ncol: usize, col_pointers: Vec, row_indices: Vec, - values: Vec, + values: Vec, symmetry: Option, ) -> Result { if nrow < 1 { @@ -230,13 +241,18 @@ impl CscMatrix { } } } - Ok(CscMatrix { - symmetry, + Ok(NumCscMatrix { + symmetry: if let Some(v) = symmetry { v } else { Symmetry::No }, nrow, ncol, col_pointers, row_indices, values, + temp_rp: Vec::new(), + temp_rj: Vec::new(), + temp_rx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }) } @@ -259,7 +275,7 @@ impl CscMatrix { /// // . . 1 . . /// // . 4 2 . 1 /// let (nrow, ncol, nnz) = (5, 5, 13); - /// let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, nnz, None)?; /// coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate /// coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate /// coo.put(1, 0, 3.0)?; @@ -308,19 +324,24 @@ impl CscMatrix { /// Ok(()) /// } /// ``` - pub fn from_coo(coo: &CooMatrix) -> Result { + pub fn from_coo(coo: &NumCooMatrix) -> Result { if coo.nnz < 1 { return Err("COO to CSC requires nnz > 0"); } - let mut csc = CscMatrix { + let mut csc = NumCscMatrix { symmetry: coo.symmetry, nrow: coo.nrow, ncol: coo.ncol, col_pointers: vec![0; coo.ncol + 1], row_indices: vec![0; coo.nnz], - values: vec![0.0; coo.nnz], + values: vec![T::zero(); coo.nnz], + temp_rp: Vec::new(), + temp_rj: Vec::new(), + temp_rx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }; - csc.update_from_coo(coo)?; + csc.update_from_coo(coo).unwrap(); Ok(csc) } @@ -331,7 +352,7 @@ impl CscMatrix { /// /// **Note:** The final nnz may be smaller than the initial nnz because duplicates /// may have been summed up. The final nnz is available as `nnz = col_pointers[ncol]`. - pub fn update_from_coo(&mut self, coo: &CooMatrix) -> Result<(), StrError> { + pub fn update_from_coo(&mut self, coo: &NumCooMatrix) -> Result<(), StrError> { // check dimensions if coo.symmetry != self.symmetry { return Err("coo.symmetry must be equal to csc.symmetry"); @@ -346,51 +367,135 @@ impl CscMatrix { return Err("coo.nnz must be equal to nnz(dup) = csc.row_indices.len() = csc.values.len()"); } - // call UMFPACK to convert COO to CSC - let status = if coo.one_based { - // handle one-based indexing - let mut indices_i = coo.indices_i.clone(); - let mut indices_j = coo.indices_j.clone(); - for k in 0..coo.nnz { - indices_i[k] -= 1; - indices_j[k] -= 1; + // Based on Prof Tim Davis' UMFPACK::UMF_triplet_map_x (umf_triplet.c) + + // constants + let nrow = coo.nrow; + let ncol = coo.ncol; + let nnz = coo.nnz; + let ndim = usize::max(nrow, ncol); + + // access the triplet data + let ai = &coo.indices_i; + let aj = &coo.indices_j; + let ax = &coo.values; + + // access the CSC data + let bp = &mut self.col_pointers; + let bi = &mut self.row_indices; + let bx = &mut self.values; + + // allocate workspaces and get an access to them + if self.temp_w.len() == 0 { + self.temp_rp = vec![0_i32; nrow + 1]; // temporary row form + self.temp_rj = vec![0_i32; nnz]; // temporary row form + self.temp_rx = vec![T::zero(); nnz]; // temporary row form + self.temp_rc = vec![0_usize; nrow]; // temporary row count + self.temp_w = vec![0_i32; ndim]; // temporary workspace + } else { + for i in 0..nrow { + self.temp_w[i] = 0; } - unsafe { - umfpack_coo_to_csc( - self.col_pointers.as_mut_ptr(), - self.row_indices.as_mut_ptr(), - self.values.as_mut_ptr(), - to_i32(coo.nrow), - to_i32(coo.ncol), - to_i32(coo.nnz), - indices_i.as_ptr(), - indices_j.as_ptr(), - coo.values.as_ptr(), - ) + } + let rp = &mut self.temp_rp; + let rj = &mut self.temp_rj; + let rx = &mut self.temp_rx; + let rc = &mut self.temp_rc; + let w = &mut self.temp_w; + + // count the entries in each row (also counting duplicates) + // use w as workspace for row counts (including duplicates) + for k in 0..nnz { + let i = ai[k] as usize; + w[i] += 1; + } + + // compute the row pointers (save them in workspace) + rp[0] = 0; + for i in 0..nrow { + rp[i + 1] = rp[i] + w[i]; + w[i] = rp[i]; + } + + // construct the row form (with unsorted values) + for k in 0..nnz { + let i = ai[k] as usize; + let p = w[i] as usize; + rj[p] = aj[k]; + rx[p] = ax[k]; + w[i] += 1; // w[i] is advanced to the start of row i+1 + } + + // sum duplicates. w[j] will hold the position in rj and rx of aij + const EMPTY: i32 = -1; + for j in 0..ncol { + w[j] = EMPTY; + } + for i in 0..nrow { + let p1 = rp[i] as usize; + let p2 = rp[i + 1] as usize; + let mut dest = p1; + // w[j] < p1 for all columns j (note that rj and rx are stored in row oriented order) + for p in p1..p2 { + let j = rj[p] as usize; + if w[j] >= p1 as i32 { + // j is already in row i, position pj + let pj = w[j] as usize; + let x = rx[p]; + rx[pj] += x; // sum the entry + } else { + // keep the entry + w[j] = dest as i32; + if dest != p { + // move is not needed + rj[dest] = j as i32; + rx[dest] = rx[p]; + } + dest += 1; + } } - } else { - unsafe { - umfpack_coo_to_csc( - self.col_pointers.as_mut_ptr(), - self.row_indices.as_mut_ptr(), - self.values.as_mut_ptr(), - to_i32(coo.nrow), - to_i32(coo.ncol), - to_i32(coo.nnz), - coo.indices_i.as_ptr(), - coo.indices_j.as_ptr(), - coo.values.as_ptr(), - ) + rc[i] = dest - p1; + } + + // count the entries in each column + for j in 0..ncol { + w[j] = 0; // use the workspace for column counts + } + for i in 0..nrow { + let p1 = rp[i] as usize; + let p2 = p1 + rc[i]; + for p in p1..p2 { + let j = rj[p] as usize; + w[j] += 1; + } + } + + // create the column pointers + bp[0] = 0; + for j in 0..ncol { + bp[j + 1] = bp[j] + w[j]; + } + for j in 0..ncol { + w[j] = bp[j]; + } + + // construct the column form + for i in 0..nrow { + let p1 = rp[i] as usize; + let p2 = p1 + rc[i]; + for p in p1..p2 { + let j = rj[p] as usize; + let cp = w[j] as usize; + bi[cp] = i as i32; + bx[cp] = rx[p]; + w[j] += 1; } - }; - if status != 0 { - return Err(handle_umfpack_error_code(status)); } Ok(()) } /// Creates a new CSC matrix from a CSR matrix - pub fn from_csr(csr: &CsrMatrix) -> Result { + pub fn from_csr(csr: &NumCsrMatrix) -> Result { // Based on the SciPy code (csr_tocsc) from here: // // https://github.com/scipy/scipy/blob/main/scipy/sparse/sparsetools/csr.h @@ -411,13 +516,18 @@ impl CscMatrix { let ax = &csr.values; // allocate the CSC arrays - let mut csc = CscMatrix { + let mut csc = NumCscMatrix { symmetry: csr.symmetry, nrow: csr.nrow, ncol: csr.ncol, col_pointers: vec![0; ncol + 1], row_indices: vec![0; nnz], - values: vec![0.0; nnz], + values: vec![T::zero(); nnz], + temp_rp: Vec::new(), + temp_rj: Vec::new(), + temp_rx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }; // access the CSC data @@ -518,8 +628,8 @@ impl CscMatrix { /// Ok(()) /// } /// ``` - pub fn as_dense(&self) -> Matrix { - let mut a = Matrix::new(self.nrow, self.ncol); + pub fn as_dense(&self) -> NumMatrix { + let mut a = NumMatrix::new(self.nrow, self.ncol); self.to_dense(&mut a).unwrap(); a } @@ -583,16 +693,13 @@ impl CscMatrix { /// Ok(()) /// } /// ``` - pub fn to_dense(&self, a: &mut Matrix) -> Result<(), StrError> { + pub fn to_dense(&self, a: &mut NumMatrix) -> Result<(), StrError> { let (m, n) = a.dims(); if m != self.nrow || n != self.ncol { return Err("wrong matrix dimensions"); } - let mirror_required = match self.symmetry { - Some(sym) => sym.triangular(), - None => false, - }; - a.fill(0.0); + let mirror_required = self.symmetry.triangular(); + a.fill(T::zero()); for j in 0..self.ncol { for p in self.col_pointers[j]..self.col_pointers[j + 1] { let i = self.row_indices[p as usize] as usize; @@ -605,66 +712,6 @@ impl CscMatrix { Ok(()) } - /// Writes a MatrixMarket file from a CooMatrix - /// - /// # Input - /// - /// * `full_path` -- may be a String, &str, or Path - /// * `vismatrix` -- generate a SMAT file for Vismatrix instead of a MatrixMarket - /// - /// **Note:** The vismatrix format is is similar to the MatrixMarket format - /// without the header, and the indices start at zero. - /// - /// # References - /// - /// * MatrixMarket: - /// * Vismatrix: - pub fn write_matrix_market

(&self, full_path: &P, vismatrix: bool) -> Result<(), StrError> - where - P: AsRef + ?Sized, - { - // output buffer - let mut buffer = String::new(); - - // handle one-based indexing - let d = if vismatrix { 0 } else { 1 }; - - // write header - if !vismatrix { - match self.symmetry { - Some(_) => write!(&mut buffer, "%%MatrixMarket matrix coordinate real symmetric\n").unwrap(), - None => write!(&mut buffer, "%%MatrixMarket matrix coordinate real general\n").unwrap(), - }; - } - - // write dimensions - let nnz = self.col_pointers[self.ncol] as usize; - write!(&mut buffer, "{} {} {}\n", self.nrow, self.ncol, nnz).unwrap(); - - // write triplets - for j in 0..self.ncol { - for p in self.col_pointers[j]..self.col_pointers[j + 1] { - let i = self.row_indices[p as usize] as usize; - let aij = self.values[p as usize]; - write!(&mut buffer, "{} {} {:?}\n", i + d, j + d, aij).unwrap(); - } - } - - // create directory - let path = Path::new(full_path); - if let Some(p) = path.parent() { - fs::create_dir_all(p).map_err(|_| "cannot create directory")?; - } - - // write file - let mut file = File::create(path).map_err(|_| "cannot create file")?; - file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; - - // force sync - file.sync_all().map_err(|_| "cannot sync file")?; - Ok(()) - } - /// Performs the matrix-vector multiplication /// /// ```text @@ -679,18 +726,15 @@ impl CscMatrix { /// # Output /// /// * `v` -- Vector with dimension equal to the number of rows of the matrix - pub fn mat_vec_mul(&self, v: &mut Vector, alpha: f64, u: &Vector) -> Result<(), StrError> { + pub fn mat_vec_mul(&self, v: &mut NumVector, alpha: T, u: &NumVector) -> Result<(), StrError> { if u.dim() != self.ncol { - return Err("u.ndim must equal ncol"); + return Err("u vector is incompatible"); } if v.dim() != self.nrow { - return Err("v.ndim must equal nrow"); + return Err("v vector is incompatible"); } - let mirror_required = match self.symmetry { - Some(sym) => sym.triangular(), - None => false, - }; - v.fill(0.0); + let mirror_required = self.symmetry.triangular(); + v.fill(T::zero()); for j in 0..self.ncol { for p in self.col_pointers[j]..self.col_pointers[j + 1] { let i = self.row_indices[p as usize] as usize; @@ -727,7 +771,7 @@ impl CscMatrix { /// assert_eq!(nrow, 1); /// assert_eq!(ncol, 2); /// assert_eq!(nnz, 2); - /// assert_eq!(symmetry, None); + /// assert_eq!(symmetry, Symmetry::No); /// let a = csc.as_dense(); /// let correct = "┌ ┐\n\ /// │ 10 20 │\n\ @@ -736,7 +780,7 @@ impl CscMatrix { /// Ok(()) /// } /// ``` - pub fn get_info(&self) -> (usize, usize, usize, Option) { + pub fn get_info(&self) -> (usize, usize, usize, Symmetry) { ( self.nrow, self.ncol, @@ -746,23 +790,47 @@ impl CscMatrix { } /// Get an access to the column pointers + /// + /// ```text + /// col_pointers.len() = ncol + 1 + /// ``` pub fn get_col_pointers(&self) -> &[i32] { &self.col_pointers } /// Get an access to the row indices + /// + /// ```text + /// nnz = col_pointers[ncol] + /// row_indices.len() == nnz + /// ``` pub fn get_row_indices(&self) -> &[i32] { - &self.row_indices + let nnz = self.col_pointers[self.ncol] as usize; + &self.row_indices[..nnz] } /// Get an access to the values - pub fn get_values(&self) -> &[f64] { - &self.values + /// + /// ```text + /// nnz = col_pointers[ncol] + /// values.len() == nnz + /// ``` + pub fn get_values(&self) -> &[T] { + let nnz = self.col_pointers[self.ncol] as usize; + &self.values[..nnz] } /// Get a mutable access to the values - pub fn get_values_mut(&mut self) -> &mut [f64] { - &mut self.values + /// + /// ```text + /// nnz = col_pointers[ncol] + /// values.len() == nnz + /// ``` + /// + /// Note: the values may be modified externally, but not the pointers or indices. + pub fn get_values_mut(&mut self) -> &mut [T] { + let nnz = self.col_pointers[self.ncol] as usize; + &mut self.values[..nnz] } } @@ -770,51 +838,51 @@ impl CscMatrix { #[cfg(test)] mod tests { - use super::CscMatrix; + use super::NumCscMatrix; use crate::{CooMatrix, Samples, Storage, Symmetry}; - use russell_lab::{vec_approx_eq, Matrix, Vector}; - use std::fs; + use num_complex::Complex64; + use russell_lab::{complex_vec_approx_eq, cpx, vec_approx_eq, ComplexVector, Matrix, Vector}; #[test] fn new_captures_errors() { assert_eq!( - CscMatrix::new(0, 1, vec![0], vec![], vec![], None).err(), + NumCscMatrix::::new(0, 1, vec![0], vec![], vec![], None).err(), Some("nrow must be ≥ 1") ); assert_eq!( - CscMatrix::new(1, 0, vec![0], vec![], vec![], None).err(), + NumCscMatrix::::new(1, 0, vec![0], vec![], vec![], None).err(), Some("ncol must be ≥ 1") ); assert_eq!( - CscMatrix::new(1, 1, vec![0], vec![], vec![], None).err(), + NumCscMatrix::::new(1, 1, vec![0], vec![], vec![], None).err(), Some("col_pointers.len() must be = ncol + 1") ); assert_eq!( - CscMatrix::new(1, 1, vec![0, 0], vec![], vec![], None).err(), + NumCscMatrix::::new(1, 1, vec![0, 0], vec![], vec![], None).err(), Some("nnz = col_pointers[ncol] must be ≥ 1") ); assert_eq!( - CscMatrix::new(1, 1, vec![0, 1], vec![], vec![], None).err(), + NumCscMatrix::::new(1, 1, vec![0, 1], vec![], vec![], None).err(), Some("row_indices.len() must be ≥ nnz") ); assert_eq!( - CscMatrix::new(1, 1, vec![0, 1], vec![0], vec![], None).err(), + NumCscMatrix::::new(1, 1, vec![0, 1], vec![0], vec![], None).err(), Some("values.len() must be ≥ nnz") ); assert_eq!( - CscMatrix::new(1, 1, vec![-1, 1], vec![0], vec![0.0], None).err(), + NumCscMatrix::::new(1, 1, vec![-1, 1], vec![0], vec![0.0], None).err(), Some("col pointers must be ≥ 0") ); assert_eq!( - CscMatrix::new(1, 1, vec![2, 1], vec![0], vec![0.0], None).err(), + NumCscMatrix::::new(1, 1, vec![2, 1], vec![0], vec![0.0], None).err(), Some("col pointers must be sorted in ascending order") ); assert_eq!( - CscMatrix::new(1, 1, vec![0, 1], vec![-1], vec![0.0], None).err(), + NumCscMatrix::::new(1, 1, vec![0, 1], vec![-1], vec![0.0], None).err(), Some("row indices must be ≥ 0") ); assert_eq!( - CscMatrix::new(1, 1, vec![0, 1], vec![2], vec![0.0], None).err(), + NumCscMatrix::::new(1, 1, vec![0, 1], vec![2], vec![0.0], None).err(), Some("row indices must be < nrow") ); // ┌ ┐ @@ -827,16 +895,16 @@ mod tests { let row_indices = vec![1, 0]; // << incorrect, should be [0, 1] let col_pointers = vec![0, 2]; assert_eq!( - CscMatrix::new(2, 1, col_pointers, row_indices, values, None).err(), + NumCscMatrix::::new(2, 1, col_pointers, row_indices, values, None).err(), Some("row indices must be sorted in ascending order (within their column)") ); } #[test] fn new_works() { - let (_, csc_correct, _, _) = Samples::rectangular_1x2(false, false, false); - let csc = CscMatrix::new(1, 2, vec![0, 1, 2], vec![0, 0], vec![10.0, 20.0], None).unwrap(); - assert_eq!(csc.symmetry, None); + let (_, csc_correct, _, _) = Samples::rectangular_1x2(false, false); + let csc = NumCscMatrix::::new(1, 2, vec![0, 1, 2], vec![0, 0], vec![10.0, 20.0], None).unwrap(); + assert_eq!(csc.symmetry, Symmetry::No); assert_eq!(csc.nrow, 1); assert_eq!(csc.ncol, 2); assert_eq!(&csc.col_pointers, &csc_correct.col_pointers); @@ -846,8 +914,11 @@ mod tests { #[test] fn from_coo_captures_errors() { - let coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); - assert_eq!(CscMatrix::from_coo(&coo).err(), Some("COO to CSC requires nnz > 0")); + let coo = CooMatrix::new(1, 1, 1, None).unwrap(); + assert_eq!( + NumCscMatrix::::from_coo(&coo).err(), + Some("COO to CSC requires nnz > 0") + ); } #[test] @@ -856,81 +927,78 @@ mod tests { // ┌ ┐ // │ 123 │ // └ ┘ - Samples::tiny_1x1(false), - Samples::tiny_1x1(true), + Samples::tiny_1x1(), // 1 . 2 // . 0 3 // 4 5 6 - Samples::unsymmetric_3x3(false, false, false), - Samples::unsymmetric_3x3(false, true, false), - Samples::unsymmetric_3x3(false, false, true), - Samples::unsymmetric_3x3(false, true, true), - Samples::unsymmetric_3x3(true, false, false), - Samples::unsymmetric_3x3(true, true, false), - Samples::unsymmetric_3x3(true, false, true), - Samples::unsymmetric_3x3(true, true, true), + Samples::unsymmetric_3x3(false, false), + Samples::unsymmetric_3x3(false, true), + Samples::unsymmetric_3x3(true, false), + Samples::unsymmetric_3x3(true, true), // 2 3 . . . // 3 . 4 . 6 // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - Samples::umfpack_unsymmetric_5x5(false), - Samples::umfpack_unsymmetric_5x5(true), + Samples::umfpack_unsymmetric_5x5(), // 1 -1 . -3 . // -2 5 . . . // . . 4 6 4 // -4 . 2 7 . // . 8 . . -5 - Samples::mkl_unsymmetric_5x5(false), - Samples::mkl_unsymmetric_5x5(true), + Samples::mkl_unsymmetric_5x5(), // 1 2 . . . // 3 4 . . . // . . 5 6 . // . . 7 8 . // . . . . 9 - Samples::block_unsymmetric_5x5(false, false, false), - Samples::block_unsymmetric_5x5(false, true, false), - Samples::block_unsymmetric_5x5(false, false, true), - Samples::block_unsymmetric_5x5(false, true, true), - Samples::block_unsymmetric_5x5(true, false, false), - Samples::block_unsymmetric_5x5(true, true, false), - Samples::block_unsymmetric_5x5(true, false, true), - Samples::block_unsymmetric_5x5(true, true, true), + Samples::block_unsymmetric_5x5(false, false), + Samples::block_unsymmetric_5x5(false, true), + Samples::block_unsymmetric_5x5(true, false), + Samples::block_unsymmetric_5x5(true, true), // 9 1.5 6 0.75 3 // 1.5 0.5 . . . // 6 . 12 . . // 0.75 . . 0.625 . // 3 . . . 16 - Samples::mkl_positive_definite_5x5_lower(false), - Samples::mkl_positive_definite_5x5_lower(true), - Samples::mkl_positive_definite_5x5_upper(false), - Samples::mkl_positive_definite_5x5_upper(true), - Samples::mkl_symmetric_5x5_lower(false, false, false), - Samples::mkl_symmetric_5x5_lower(false, true, false), - Samples::mkl_symmetric_5x5_lower(false, false, true), - Samples::mkl_symmetric_5x5_lower(false, true, true), - Samples::mkl_symmetric_5x5_lower(true, false, false), - Samples::mkl_symmetric_5x5_lower(true, true, false), - Samples::mkl_symmetric_5x5_lower(true, false, true), - Samples::mkl_symmetric_5x5_lower(true, true, true), - Samples::mkl_symmetric_5x5_upper(false, false, false), - Samples::mkl_symmetric_5x5_upper(false, true, false), - Samples::mkl_symmetric_5x5_upper(false, false, true), - Samples::mkl_symmetric_5x5_upper(false, true, true), - Samples::mkl_symmetric_5x5_upper(true, false, false), - Samples::mkl_symmetric_5x5_upper(true, true, false), - Samples::mkl_symmetric_5x5_upper(true, false, true), - Samples::mkl_symmetric_5x5_upper(true, true, true), - Samples::mkl_symmetric_5x5_full(false), - Samples::mkl_symmetric_5x5_full(true), + Samples::mkl_positive_definite_5x5_lower(), + Samples::mkl_positive_definite_5x5_upper(), + Samples::mkl_symmetric_5x5_lower(false, false), + Samples::mkl_symmetric_5x5_lower(false, true), + Samples::mkl_symmetric_5x5_lower(true, false), + Samples::mkl_symmetric_5x5_lower(true, true), + Samples::mkl_symmetric_5x5_upper(false, false), + Samples::mkl_symmetric_5x5_upper(false, true), + Samples::mkl_symmetric_5x5_upper(true, false), + Samples::mkl_symmetric_5x5_upper(true, true), + Samples::mkl_symmetric_5x5_full(), + // ┌ ┐ + // │ 10 20 │ + // └ ┘ + Samples::rectangular_1x2(false, false), + Samples::rectangular_1x2(false, true), + Samples::rectangular_1x2(true, false), + Samples::rectangular_1x2(true, true), // ┌ ┐ // │ 1 . 3 . 5 . 7 │ // └ ┘ Samples::rectangular_1x7(), + // ┌ ┐ + // │ . │ + // │ 2 │ + // │ . │ + // │ 4 │ + // │ . │ + // │ 6 │ + // │ . │ + // └ ┘ Samples::rectangular_7x1(), + // 5 -2 . 1 + // 10 -4 . 2 + // 15 -6 . 3 Samples::rectangular_3x4(), ] { - let csc = CscMatrix::from_coo(&coo).unwrap(); + let csc = NumCscMatrix::::from_coo(&coo).unwrap(); assert_eq!(&csc.col_pointers, &csc_correct.col_pointers); let nnz = csc.col_pointers[csc.ncol] as usize; assert_eq!(&csc.row_indices[0..nnz], &csc_correct.row_indices); @@ -941,19 +1009,20 @@ mod tests { #[test] #[rustfmt::skip] fn update_from_coo_captures_errors() { - let (coo, _, _, _) = Samples::rectangular_1x2(false, false, false); - let mut csc = CscMatrix::from_coo(&coo).unwrap(); - let sym = Some(Symmetry::General(Storage::Lower)); - assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: sym, nrow: 1, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0], one_based: false }).err(), Some("coo.symmetry must be equal to csc.symmetry")); - assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: None, nrow: 2, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0], one_based: false }).err(), Some("coo.nrow must be equal to csc.nrow")); - assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: None, nrow: 1, ncol: 1, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0], one_based: false }).err(), Some("coo.ncol must be equal to csc.ncol")); - assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: None, nrow: 1, ncol: 2, nnz: 3, max_nnz: 3, indices_i: vec![0,0,0], indices_j: vec![0,0,0], values: vec![0.0,0.0,0.0], one_based: false }).err(), Some("coo.nnz must be equal to nnz(dup) = csc.row_indices.len() = csc.values.len()")); + let (coo, _, _, _) = Samples::rectangular_1x2(false, false, ); + let mut csc = NumCscMatrix::::from_coo(&coo).unwrap(); + let yes = Symmetry::General(Storage::Lower); + let no = Symmetry::No; + assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: yes, nrow: 1, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0] }).err(), Some("coo.symmetry must be equal to csc.symmetry")); + assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: no, nrow: 2, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0] }).err(), Some("coo.nrow must be equal to csc.nrow")); + assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: no, nrow: 1, ncol: 1, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0] }).err(), Some("coo.ncol must be equal to csc.ncol")); + assert_eq!(csc.update_from_coo(&CooMatrix { symmetry: no, nrow: 1, ncol: 2, nnz: 3, max_nnz: 3, indices_i: vec![0,0,0], indices_j: vec![0,0,0], values: vec![0.0,0.0,0.0] }).err(), Some("coo.nnz must be equal to nnz(dup) = csc.row_indices.len() = csc.values.len()")); } #[test] fn update_from_coo_again_works() { - let (coo, csc_correct, _, _) = Samples::umfpack_unsymmetric_5x5(false); - let mut csc = CscMatrix::from_coo(&coo).unwrap(); + let (coo, csc_correct, _, _) = Samples::umfpack_unsymmetric_5x5(); + let mut csc = NumCscMatrix::::from_coo(&coo).unwrap(); assert_eq!(&csc.col_pointers, &csc_correct.col_pointers); let nnz = csc.col_pointers[csc.ncol] as usize; assert_eq!(&csc.row_indices[0..nnz], &csc_correct.row_indices); @@ -973,46 +1042,62 @@ mod tests { // ┌ ┐ // │ 123 │ // └ ┘ - Samples::tiny_1x1(IGNORED), + Samples::tiny_1x1(), // 1 . 2 // . 0 3 // 4 5 6 - Samples::unsymmetric_3x3(IGNORED, IGNORED, IGNORED), + Samples::unsymmetric_3x3(IGNORED, IGNORED), // 2 3 . . . // 3 . 4 . 6 // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - Samples::umfpack_unsymmetric_5x5(IGNORED), + Samples::umfpack_unsymmetric_5x5(), // 1 -1 . -3 . // -2 5 . . . // . . 4 6 4 // -4 . 2 7 . // . 8 . . -5 - Samples::mkl_unsymmetric_5x5(IGNORED), + Samples::mkl_unsymmetric_5x5(), // 1 2 . . . // 3 4 . . . // . . 5 6 . // . . 7 8 . // . . . . 9 - Samples::block_unsymmetric_5x5(IGNORED, IGNORED, IGNORED), + Samples::block_unsymmetric_5x5(IGNORED, IGNORED), // 9 1.5 6 0.75 3 // 1.5 0.5 . . . // 6 . 12 . . // 0.75 . . 0.625 . // 3 . . . 16 - Samples::mkl_positive_definite_5x5_lower(IGNORED), - Samples::mkl_symmetric_5x5_lower(IGNORED, IGNORED, IGNORED), - Samples::mkl_symmetric_5x5_upper(IGNORED, IGNORED, IGNORED), - Samples::mkl_symmetric_5x5_full(IGNORED), + Samples::mkl_positive_definite_5x5_lower(), + Samples::mkl_symmetric_5x5_lower(IGNORED, IGNORED), + Samples::mkl_symmetric_5x5_upper(IGNORED, IGNORED), + Samples::mkl_symmetric_5x5_full(), + // ┌ ┐ + // │ 10 20 │ + // └ ┘ + Samples::rectangular_1x2(IGNORED, IGNORED), // ┌ ┐ // │ 1 . 3 . 5 . 7 │ // └ ┘ Samples::rectangular_1x7(), + // ┌ ┐ + // │ . │ + // │ 2 │ + // │ . │ + // │ 4 │ + // │ . │ + // │ 6 │ + // │ . │ + // └ ┘ Samples::rectangular_7x1(), + // 5 -2 . 1 + // 10 -4 . 2 + // 15 -6 . 3 Samples::rectangular_3x4(), ] { - let csc = CscMatrix::from_csr(&csr).unwrap(); + let csc = NumCscMatrix::::from_csr(&csr).unwrap(); assert_eq!(&csc.col_pointers, &csc_correct.col_pointers); assert_eq!(&csc.row_indices, &csc_correct.row_indices); vec_approx_eq(&csc.values, &csc_correct.values, 1e-15); @@ -1021,15 +1106,7 @@ mod tests { #[test] fn to_matrix_fails_on_wrong_dims() { - // 10.0 20.0 << (1 x 2) matrix - let csc = CscMatrix { - symmetry: None, - nrow: 1, - ncol: 2, - col_pointers: vec![0, 1, 2], - row_indices: vec![0, 0], - values: vec![10.0, 20.0], - }; + let (_, csc, _, _) = Samples::rectangular_1x2(false, false); let mut a_3x1 = Matrix::new(3, 1); let mut a_1x3 = Matrix::new(1, 3); assert_eq!(csc.to_dense(&mut a_3x1), Err("wrong matrix dimensions")); @@ -1038,15 +1115,8 @@ mod tests { #[test] fn to_matrix_and_as_matrix_work() { - // 10.0 20.0 << (1 x 2) matrix - let csc = CscMatrix { - symmetry: None, - nrow: 1, - ncol: 2, - col_pointers: vec![0, 1, 2], - row_indices: vec![0, 0], - values: vec![10.0, 20.0], - }; + // 1 x 2 matrix + let (_, csc, _, _) = Samples::rectangular_1x2(false, false); let mut a = Matrix::new(1, 2); csc.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ @@ -1054,33 +1124,8 @@ mod tests { └ ┘"; assert_eq!(format!("{}", a), correct); - let csc = CscMatrix { - symmetry: None, - nrow: 5, - ncol: 5, - col_pointers: vec![0, 2, 5, 9, 10, 12], - row_indices: vec![ - // p - 0, 1, // j = 0, count = 0, 1, - 0, 2, 4, // j = 1, count = 2, 3, 4, - 1, 2, 3, 4, // j = 2, count = 5, 6, 7, 8, - 2, // j = 3, count = 9, - 1, 4, // j = 4, count = 10, 11, - // 12 - ], - values: vec![ - // p - 2.0, 3.0, // j = 0, count = 0, 1, - 3.0, -1.0, 4.0, // j = 1, count = 2, 3, 4, - 4.0, -3.0, 1.0, 2.0, // j = 2, count = 5, 6, 7, 8, - 2.0, // j = 3, count = 9, - 6.0, - 1.0, // j = 4, count = 10, 11, - // 12 - ], - }; - - // covert to dense + // 5 x 5 matrix + let (_, csc, _, _) = Samples::umfpack_unsymmetric_5x5(); let mut a = Matrix::new(5, 5); csc.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ @@ -1102,14 +1147,7 @@ mod tests { #[test] fn as_matrix_upper_works() { - let csc = CscMatrix { - symmetry: Some(Symmetry::General(Storage::Upper)), - nrow: 5, - ncol: 5, - col_pointers: vec![0, 1, 3, 5, 7, 9], - row_indices: vec![0, 0, 1, 0, 2, 0, 3, 0, 4], - values: vec![9.0, 1.5, 0.5, 6.0, 12.0, 0.75, 0.625, 3.0, 16.0], - }; + let (_, csc, _, _) = Samples::mkl_symmetric_5x5_upper(false, false); let a = csc.as_dense(); let correct = "┌ ┐\n\ │ 9 1.5 6 0.75 3 │\n\ @@ -1123,14 +1161,7 @@ mod tests { #[test] fn as_matrix_lower_works() { - let csc = CscMatrix { - symmetry: Some(Symmetry::General(Storage::Lower)), - nrow: 5, - ncol: 5, - col_pointers: vec![0, 5, 6, 7, 8, 9], - row_indices: vec![0, 1, 2, 3, 4, 1, 2, 3, 4], - values: vec![9.0, 1.5, 6.0, 0.75, 3.0, 0.5, 12.0, 0.625, 16.0], - }; + let (_, csc, _, _) = Samples::mkl_symmetric_5x5_lower(false, false); let a = csc.as_dense(); let correct = "┌ ┐\n\ │ 9 1.5 6 0.75 3 │\n\ @@ -1142,6 +1173,17 @@ mod tests { assert_eq!(format!("{}", a), correct); } + #[test] + fn mat_vec_mul_captures_errors() { + let (_, csc, _, _) = Samples::rectangular_3x4(); + let u = Vector::new(3); + let mut v = Vector::new(csc.nrow); + assert_eq!(csc.mat_vec_mul(&mut v, 2.0, &u).err(), Some("u vector is incompatible")); + let u = Vector::new(4); + let mut v = Vector::new(2); + assert_eq!(csc.mat_vec_mul(&mut v, 2.0, &u).err(), Some("v vector is incompatible")); + } + #[test] fn mat_vec_mul_works() { // 5 -2 . 1 @@ -1150,29 +1192,78 @@ mod tests { let (_, csc, _, _) = Samples::rectangular_3x4(); let u = Vector::from(&[1.0, 3.0, 8.0, 5.0]); let mut v = Vector::new(csc.nrow); - csc.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct = &[4.0, 8.0, 12.0]; + csc.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + let correct = &[8.0, 16.0, 24.0]; vec_approx_eq(v.as_data(), correct, 1e-15); // call mat_vec_mul again to make sure the vector is filled with zeros before the sum - csc.mat_vec_mul(&mut v, 1.0, &u).unwrap(); + csc.mat_vec_mul(&mut v, 2.0, &u).unwrap(); vec_approx_eq(v.as_data(), correct, 1e-15); } + #[test] + fn mat_vec_mul_symmetric_lower_works() { + let (_, csc, _, _) = Samples::mkl_symmetric_5x5_lower(false, false); + let u = Vector::from(&[1.0, 2.0, 3.0, 4.0, 5.0]); + let mut v = Vector::new(5); + csc.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + vec_approx_eq(v.as_data(), &[96.0, 5.0, 84.0, 6.5, 166.0], 1e-15); + // another test + let (_, csc, _, _) = Samples::lower_symmetric_5x5(); + let u = Vector::from(&[-629.0 / 98.0, 237.0 / 49.0, -53.0 / 49.0, 62.0 / 49.0, 23.0 / 14.0]); + let mut v = Vector::new(5); + csc.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + vec_approx_eq(v.as_data(), &[-4.0, 8.0, 6.0, -10.0, 2.0], 1e-14); + } + + #[test] + fn mat_vec_mul_complex_works() { + // 4+4i . 2+2i + // . 1 3+3i + // . 5+5i 1+1i + // 1 . . + let (_, csc, _, _) = Samples::complex_rectangular_4x3(); + let u = ComplexVector::from(&[cpx!(1.0, 1.0), cpx!(3.0, 1.0), cpx!(5.0, -1.0)]); + let mut v = ComplexVector::new(csc.nrow); + csc.mat_vec_mul(&mut v, cpx!(2.0, 4.0), &u).unwrap(); + let correct = &[ + cpx!(-40.0, 80.0), + cpx!(-10.0, 110.0), + cpx!(-64.0, 112.0), + cpx!(-2.0, 6.0), + ]; + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + // call mat_vec_mul again to make sure the vector is filled with zeros before the sum + csc.mat_vec_mul(&mut v, cpx!(2.0, 4.0), &u).unwrap(); + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + } + #[test] fn getters_are_correct() { - let (_, csc, _, _) = Samples::rectangular_1x2(false, false, false); - assert_eq!(csc.get_info(), (1, 2, 2, None)); + let (_, csc, _, _) = Samples::rectangular_1x2(false, false); + assert_eq!(csc.get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(csc.get_col_pointers(), &[0, 1, 2]); assert_eq!(csc.get_row_indices(), &[0, 0]); assert_eq!(csc.get_values(), &[10.0, 20.0]); - - let mut csc = CscMatrix { - symmetry: None, + // with duplicates + let (coo, _, _, _) = Samples::rectangular_1x2(false, false); + let csc = NumCscMatrix::::from_coo(&coo).unwrap(); + assert_eq!(csc.get_info(), (1, 2, 2, Symmetry::No)); + assert_eq!(csc.get_col_pointers(), &[0, 1, 2]); + assert_eq!(csc.get_row_indices(), &[0, 0]); + assert_eq!(csc.get_values(), &[10.0, 20.0]); + // mutable + let mut csc = NumCscMatrix:: { + symmetry: Symmetry::No, nrow: 1, ncol: 2, values: vec![10.0, 20.0], row_indices: vec![0, 0], col_pointers: vec![0, 1, 2], + temp_rp: Vec::new(), + temp_rj: Vec::new(), + temp_rx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }; let x = csc.get_values_mut(); x.reverse(); @@ -1180,41 +1271,37 @@ mod tests { } #[test] - fn write_matrix_market_works() { - // 2 3 . . . - // 3 . 4 . 6 - // . -1 -3 2 . - // . . 1 . . - // . 4 2 . 1 - let (_, csc, _, _) = Samples::umfpack_unsymmetric_5x5(false); - let full_path = "/tmp/russell_sparse/test_write_matrix_market_csc.mtx"; - csc.write_matrix_market(full_path, false).unwrap(); - let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); - assert_eq!( - contents, - "%%MatrixMarket matrix coordinate real general\n\ - 5 5 12\n\ - 1 1 2.0\n\ - 2 1 3.0\n\ - 1 2 3.0\n\ - 3 2 -1.0\n\ - 5 2 4.0\n\ - 2 3 4.0\n\ - 3 3 -3.0\n\ - 4 3 1.0\n\ - 5 3 2.0\n\ - 3 4 2.0\n\ - 2 5 6.0\n\ - 5 5 1.0\n" - ); - } - - #[test] - fn clone_works() { - let (_, csc, _, _) = Samples::tiny_1x1(false); + fn derive_methods_work() { + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); + let csc = NumCscMatrix::::from_coo(&coo).unwrap(); + let nrow = coo.nrow; + let nnz = coo.nnz; // it must be COO nnz because of (removed) duplicates + assert_eq!(csc.temp_rp.len(), nrow + 1); + assert_eq!(csc.temp_rj.len(), nnz); + assert_eq!(csc.temp_rx.len(), nnz); + assert_eq!(csc.temp_rc.len(), nrow); + assert_eq!(csc.temp_w.len(), nrow); let mut clone = csc.clone(); clone.values[0] *= 2.0; - assert_eq!(csc.values[0], 123.0); - assert_eq!(clone.values[0], 246.0); + assert_eq!(csc.values[0], 2.0); + assert_eq!(clone.values[0], 4.0); + assert!(format!("{:?}", csc).len() > 0); + let json = serde_json::to_string(&csc).unwrap(); + assert_eq!( + json, + r#"{"symmetry":"No","nrow":5,"ncol":5,"col_pointers":[0,2,5,9,10,12],"row_indices":[0,1,0,2,4,1,2,3,4,2,1,4,0],"values":[2.0,3.0,3.0,-1.0,4.0,4.0,-3.0,1.0,2.0,2.0,6.0,1.0,0.0]}"# + ); + let from_json: NumCscMatrix = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json.symmetry, csc.symmetry); + assert_eq!(from_json.nrow, csc.nrow); + assert_eq!(from_json.ncol, csc.ncol); + assert_eq!(from_json.col_pointers, csc.col_pointers); + assert_eq!(from_json.row_indices, csc.row_indices); + assert_eq!(from_json.values, csc.values); + assert_eq!(from_json.temp_rp.len(), 0); + assert_eq!(from_json.temp_rj.len(), 0); + assert_eq!(from_json.temp_rx.len(), 0); + assert_eq!(from_json.temp_rc.len(), 0); + assert_eq!(from_json.temp_w.len(), 0); } } diff --git a/russell_sparse/src/csr_matrix.rs b/russell_sparse/src/csr_matrix.rs index c11bbd58..af0e5f42 100644 --- a/russell_sparse/src/csr_matrix.rs +++ b/russell_sparse/src/csr_matrix.rs @@ -1,11 +1,10 @@ -use super::{to_i32, CooMatrix, CscMatrix, Symmetry}; +use super::{to_i32, NumCooMatrix, NumCscMatrix, Symmetry}; use crate::StrError; -use russell_lab::{Matrix, Vector}; -use std::ffi::OsStr; -use std::fmt::Write; -use std::fs::{self, File}; -use std::io::Write as IoWrite; -use std::path::Path; +use num_traits::{Num, NumCast}; +use russell_lab::{NumMatrix, NumVector}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::ops::{AddAssign, MulAssign}; /// Holds the arrays needed for a CSR (compressed sparse row) matrix /// @@ -48,13 +47,15 @@ use std::path::Path; /// ```text /// 0, 3, 5, 8, 11, 13 /// ``` -#[derive(Clone)] -pub struct CsrMatrix { +/// +/// **Note:** The number of non-zero values is `nnz = row_pointers[nrow]` +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct NumCsrMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ /// Defines the symmetry and storage: lower-triangular, upper-triangular, full-matrix - /// - /// **Note:** `None` means unsymmetric matrix or unspecified symmetry, - /// where the storage is automatically `Full`. - pub(crate) symmetry: Option, + pub(crate) symmetry: Symmetry, /// Holds the number of rows (must fit i32) pub(crate) nrow: usize, @@ -66,7 +67,7 @@ pub struct CsrMatrix { /// /// ```text /// row_pointers.len() = nrow + 1 - /// nnz = col_pointers[ncol] + /// nnz = row_pointers[nrow] /// ``` pub(crate) row_pointers: Vec, @@ -84,10 +85,30 @@ pub struct CsrMatrix { /// nnz_dup ≥ nnz /// values.len() = nnz_dup /// ``` - pub(crate) values: Vec, + #[serde(bound(deserialize = "Vec: Deserialize<'de>"))] + pub(crate) values: Vec, + + /// Temporary row form (for COO to CSR conversion) + #[serde(skip)] + temp_rp: Vec, + + /// Temporary row form (for COO to CSR conversion) + #[serde(skip)] + temp_rjx: Vec<(i32, T)>, + + /// Temporary row count (for COO to CSR conversion) + #[serde(skip)] + temp_rc: Vec, + + /// Temporary workspace (for COO to CSR conversion) + #[serde(skip)] + temp_w: Vec, } -impl CsrMatrix { +impl NumCsrMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ /// Creates a new CSR matrix from (sorted) data arrays /// /// **Note:** The row pointers and column indices must be **sorted** in ascending order. @@ -169,7 +190,7 @@ impl CsrMatrix { ncol: usize, row_pointers: Vec, col_indices: Vec, - values: Vec, + values: Vec, symmetry: Option, ) -> Result { if nrow < 1 { @@ -216,13 +237,17 @@ impl CsrMatrix { } } } - Ok(CsrMatrix { - symmetry, + Ok(NumCsrMatrix { + symmetry: if let Some(v) = symmetry { v } else { Symmetry::No }, nrow, ncol, row_pointers, col_indices, values, + temp_rp: Vec::new(), + temp_rjx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }) } @@ -245,7 +270,7 @@ impl CsrMatrix { /// // . . 1 . . /// // . 4 2 . 1 /// let (nrow, ncol, nnz) = (5, 5, 13); - /// let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false)?; + /// let mut coo = CooMatrix::new(nrow, ncol, nnz, None)?; /// coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate /// coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate /// coo.put(1, 0, 3.0)?; @@ -294,19 +319,23 @@ impl CsrMatrix { /// Ok(()) /// } /// ``` - pub fn from_coo(coo: &CooMatrix) -> Result { + pub fn from_coo(coo: &NumCooMatrix) -> Result { if coo.nnz < 1 { return Err("COO to CSR requires nnz > 0"); } - let mut csr = CsrMatrix { + let mut csr = NumCsrMatrix { symmetry: coo.symmetry, nrow: coo.nrow, ncol: coo.ncol, row_pointers: vec![0; coo.nrow + 1], col_indices: vec![0; coo.nnz], - values: vec![0.0; coo.nnz], + values: vec![T::zero(); coo.nnz], + temp_rp: Vec::new(), + temp_rjx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }; - csr.update_from_coo(coo)?; + csr.update_from_coo(coo).unwrap(); Ok(csr) } @@ -317,17 +346,7 @@ impl CsrMatrix { /// /// **Note:** The final nnz may be smaller than the initial nnz because duplicates /// may have been summed up. The final nnz is available as `nnz = row_pointers[nrow]`. - pub fn update_from_coo(&mut self, coo: &CooMatrix) -> Result<(), StrError> { - // Based on the SciPy code (coo_tocsr) from here: - // - // https://github.com/scipy/scipy/blob/main/scipy/sparse/sparsetools/coo.h - // - // Notes: - // - // * The row and column indices may be unordered - // * Linear complexity: O(nnz(A) + max(nrow, ncol)) - // * Upgrading i32 to usize is OK (the opposite is not OK => use to_i32) - + pub fn update_from_coo(&mut self, coo: &NumCooMatrix) -> Result<(), StrError> { // check dimensions if coo.symmetry != self.symmetry { return Err("coo.symmetry must be equal to csr.symmetry"); @@ -342,9 +361,13 @@ impl CsrMatrix { return Err("coo.nnz must be equal to nnz(dup) = self.col_indices.len() = csr.values.len()"); } + // Based on Prof Tim Davis' UMFPACK::UMF_triplet_map_x (umf_triplet.c) + // constants let nrow = coo.nrow; + let ncol = coo.ncol; let nnz = coo.nnz; + let ndim = usize::max(nrow, ncol); // access the triplet data let ai = &coo.indices_i; @@ -356,69 +379,98 @@ impl CsrMatrix { let bj = &mut self.col_indices; let bx = &mut self.values; - // handle one-based indexing - let d = if coo.one_based { -1 } else { 0 }; + // allocate workspaces and get an access to them + if self.temp_w.len() == 0 { + self.temp_rp = vec![0_i32; nrow + 1]; // temporary row form + self.temp_rjx = vec![(0_i32, T::zero()); nnz]; // temporary row form + self.temp_rc = vec![0_usize; nrow]; // temporary row count + self.temp_w = vec![0_i32; ndim]; // temporary workspace + } else { + for i in 0..nrow { + self.temp_w[i] = 0; + } + } + let rp = &mut self.temp_rp; + let rjx = &mut self.temp_rjx; + let rc = &mut self.temp_rc; + let w = &mut self.temp_w; - // compute number of non-zero entries per row of A - bp.fill(0); + // count the entries in each row (also counting duplicates) + // use w as workspace for row counts (including duplicates) for k in 0..nnz { - bp[(ai[k] + d) as usize] += 1; + let i = ai[k] as usize; + w[i] += 1; } - // perform the cumulative sum of the nnz per row to get bp - let mut sum = 0; + // compute the row pointers (save them in workspace) + rp[0] = 0; for i in 0..nrow { - let temp = bp[i]; - bp[i] = sum; - sum += temp; + rp[i + 1] = rp[i] + w[i]; + w[i] = rp[i]; } - bp[nrow] = to_i32(nnz); - // write aj and ax into bj and bx (will use bp as workspace) + // construct the row form (with unsorted values) for k in 0..nnz { - let i = (ai[k] + d) as usize; - let dest = bp[i] as usize; - bj[dest] = aj[k] + d; - bx[dest] = ax[k]; - bp[i] += 1; + let i = ai[k] as usize; + let p = w[i] as usize; + rjx[p].0 = aj[k]; + rjx[p].1 = ax[k]; + w[i] += 1; // w[i] is advanced to the start of row i+1 } - // fix bp - let mut last = 0; - for i in 0..(nrow + 1) { - let temp = bp[i]; - bp[i] = last; - last = temp; + // sum duplicates. w[j] will hold the position in rj and rx of aij + const EMPTY: i32 = -1; + for j in 0..ncol { + w[j] = EMPTY; } - - // sort rows - let mut temp: Vec<(i32, f64)> = Vec::new(); for i in 0..nrow { - let row_start = bp[i]; - let row_end = bp[i + 1]; - temp.resize((row_end - row_start) as usize, (0, 0.0)); - let mut n = 0; - for p in row_start..row_end { - temp[n].0 = bj[p as usize]; - temp[n].1 = bx[p as usize]; - n += 1; - } - temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - n = 0; - for p in row_start..row_end { - bj[p as usize] = temp[n].0; - bx[p as usize] = temp[n].1; - n += 1; + let p1 = rp[i] as usize; + let p2 = rp[i + 1] as usize; + let mut dest = p1; + // w[j] < p1 for all columns j (note that rj and rx are stored in row oriented order) + for p in p1..p2 { + let j = rjx[p].0 as usize; + if w[j] >= p1 as i32 { + // j is already in row i, position pj + let pj = w[j] as usize; + let x = rjx[p].1; + rjx[pj].1 += x; // sum the entry + } else { + // keep the entry + w[j] = dest as i32; + if dest != p { + rjx[dest].0 = j as i32; + rjx[dest].1 = rjx[p].1; + } + dest += 1; + } } + rc[i] = dest - p1; + } + + // fix row pointers + bp[0] = 0; + for i in 0..nrow { + bp[i + 1] = bp[i] + (rc[i] as i32); } - // sum duplicates - csr_sum_duplicates(nrow, bp, bj, bx); + // construct the row form (with sorted values) + let mut k = 0; + for i in 0..nrow { + let p1 = rp[i] as usize; + let p2 = p1 + rc[i]; + rjx[p1..p2].sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + for (j, x) in &rjx[p1..p2] { + bj[k] = *j; + bx[k] = *x; + k += 1; + } + } Ok(()) } /// Creates a new CSR matrix from a CSC matrix - pub fn from_csc(csc: &CscMatrix) -> Result { + pub fn from_csc(csc: &NumCscMatrix) -> Result { // Based on the SciPy code (csr_tocsc) from here: // // https://github.com/scipy/scipy/blob/main/scipy/sparse/sparsetools/csr.h @@ -440,13 +492,17 @@ impl CsrMatrix { let ax = &csc.values; // allocate the CSR arrays - let mut csr = CsrMatrix { + let mut csr = NumCsrMatrix { symmetry: csc.symmetry, ncol: csc.ncol, nrow: csc.nrow, row_pointers: vec![0; nrow + 1], col_indices: vec![0; nnz], - values: vec![0.0; nnz], + values: vec![T::zero(); nnz], + temp_rp: Vec::new(), + temp_rjx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }; // access the CSR data @@ -546,8 +602,8 @@ impl CsrMatrix { /// Ok(()) /// } /// ``` - pub fn as_dense(&self) -> Matrix { - let mut a = Matrix::new(self.nrow, self.ncol); + pub fn as_dense(&self) -> NumMatrix { + let mut a = NumMatrix::new(self.nrow, self.ncol); self.to_dense(&mut a).unwrap(); a } @@ -611,16 +667,13 @@ impl CsrMatrix { /// Ok(()) /// } /// ``` - pub fn to_dense(&self, a: &mut Matrix) -> Result<(), StrError> { + pub fn to_dense(&self, a: &mut NumMatrix) -> Result<(), StrError> { let (m, n) = a.dims(); if m != self.nrow || n != self.ncol { return Err("wrong matrix dimensions"); } - let mirror_required = match self.symmetry { - Some(sym) => sym.triangular(), - None => false, - }; - a.fill(0.0); + let mirror_required = self.symmetry.triangular(); + a.fill(T::zero()); for i in 0..self.nrow { for p in self.row_pointers[i]..self.row_pointers[i + 1] { let j = self.col_indices[p as usize] as usize; @@ -633,66 +686,6 @@ impl CsrMatrix { Ok(()) } - /// Writes a MatrixMarket file from a CooMatrix - /// - /// # Input - /// - /// * `full_path` -- may be a String, &str, or Path - /// * `vismatrix` -- generate a SMAT file for Vismatrix instead of a MatrixMarket - /// - /// **Note:** The vismatrix format is is similar to the MatrixMarket format - /// without the header, and the indices start at zero. - /// - /// # References - /// - /// * MatrixMarket: - /// * Vismatrix: - pub fn write_matrix_market

(&self, full_path: &P, vismatrix: bool) -> Result<(), StrError> - where - P: AsRef + ?Sized, - { - // output buffer - let mut buffer = String::new(); - - // handle one-based indexing - let d = if vismatrix { 0 } else { 1 }; - - // write header - if !vismatrix { - match self.symmetry { - Some(_) => write!(&mut buffer, "%%MatrixMarket matrix coordinate real symmetric\n").unwrap(), - None => write!(&mut buffer, "%%MatrixMarket matrix coordinate real general\n").unwrap(), - }; - } - - // write dimensions - let nnz = self.row_pointers[self.nrow] as usize; - write!(&mut buffer, "{} {} {}\n", self.nrow, self.ncol, nnz).unwrap(); - - // write triplets - for i in 0..self.nrow { - for p in self.row_pointers[i]..self.row_pointers[i + 1] { - let j = self.col_indices[p as usize] as usize; - let aij = self.values[p as usize]; - write!(&mut buffer, "{} {} {:?}\n", i + d, j + d, aij).unwrap(); - } - } - - // create directory - let path = Path::new(full_path); - if let Some(p) = path.parent() { - fs::create_dir_all(p).map_err(|_| "cannot create directory")?; - } - - // write file - let mut file = File::create(path).map_err(|_| "cannot create file")?; - file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; - - // force sync - file.sync_all().map_err(|_| "cannot sync file")?; - Ok(()) - } - /// Performs the matrix-vector multiplication /// /// ```text @@ -707,18 +700,15 @@ impl CsrMatrix { /// # Output /// /// * `v` -- Vector with dimension equal to the number of rows of the matrix - pub fn mat_vec_mul(&self, v: &mut Vector, alpha: f64, u: &Vector) -> Result<(), StrError> { + pub fn mat_vec_mul(&self, v: &mut NumVector, alpha: T, u: &NumVector) -> Result<(), StrError> { if u.dim() != self.ncol { - return Err("u.ndim must equal ncol"); + return Err("u vector is incompatible"); } if v.dim() != self.nrow { - return Err("v.ndim must equal nrow"); + return Err("v vector is incompatible"); } - let mirror_required = match self.symmetry { - Some(sym) => sym.triangular(), - None => false, - }; - v.fill(0.0); + let mirror_required = self.symmetry.triangular(); + v.fill(T::zero()); for i in 0..self.nrow { for p in self.row_pointers[i]..self.row_pointers[i + 1] { let j = self.col_indices[p as usize] as usize; @@ -755,7 +745,7 @@ impl CsrMatrix { /// assert_eq!(nrow, 1); /// assert_eq!(ncol, 2); /// assert_eq!(nnz, 2); - /// assert_eq!(symmetry, None); + /// assert_eq!(symmetry, Symmetry::No); /// let a = csr.as_dense(); /// let correct = "┌ ┐\n\ /// │ 10 20 │\n\ @@ -764,7 +754,7 @@ impl CsrMatrix { /// Ok(()) /// } /// ``` - pub fn get_info(&self) -> (usize, usize, usize, Option) { + pub fn get_info(&self) -> (usize, usize, usize, Symmetry) { ( self.nrow, self.ncol, @@ -774,112 +764,99 @@ impl CsrMatrix { } /// Get an access to the row pointers + /// + /// ```text + /// row_pointers.len() = nrow + 1 + /// ``` pub fn get_row_pointers(&self) -> &[i32] { &self.row_pointers } /// Get an access to the column indices + /// + /// ```text + /// nnz = row_pointers[nrow] + /// col_indices.len() == nnz + /// ``` pub fn get_col_indices(&self) -> &[i32] { - &self.col_indices + let nnz = self.row_pointers[self.nrow] as usize; + &self.col_indices[..nnz] } /// Get an access to the values - pub fn get_values(&self) -> &[f64] { - &self.values + /// + /// ```text + /// nnz = row_pointers[nrow] + /// values.len() == nnz + /// ``` + pub fn get_values(&self) -> &[T] { + let nnz = self.row_pointers[self.nrow] as usize; + &self.values[..nnz] } /// Get a mutable access to the values - pub fn get_values_mut(&mut self) -> &mut [f64] { - &mut self.values - } -} - -/// brief Sums duplicate column entries in each row of a CSR matrix -/// -/// Returns The final number of non-zeros (nnz) after duplicates have been handled -fn csr_sum_duplicates(nrow: usize, ap: &mut [i32], aj: &mut [i32], ax: &mut [f64]) -> usize { - // Based on the SciPy code from here: - // - // https://github.com/scipy/scipy/blob/main/scipy/sparse/sparsetools/csr.h - // - // * ap[n_row+1] -- row pointer - // * aj[nnz(A)] -- column indices - // * ax[nnz(A)] -- non-zeros - // * The column indices within each row must be sorted - // * Explicit zeros are retained - // * ap, aj, and ax will be modified in place - - let mut nnz: i32 = 0; - let mut row_end = 0; - for i in 0..nrow { - let mut k = row_end; - row_end = ap[i + 1]; - while k < row_end { - let j = aj[k as usize]; - let mut x = ax[k as usize]; - k += 1; - while k < row_end && aj[k as usize] == j { - x += ax[k as usize]; - k += 1; - } - aj[nnz as usize] = j; - ax[nnz as usize] = x; - nnz += 1; - } - ap[i + 1] = nnz; + /// + /// ```text + /// nnz = row_pointers[nrow] + /// values.len() == nnz + /// ``` + /// + /// Note: the values may be modified externally, but not the pointers or indices. + pub fn get_values_mut(&mut self) -> &mut [T] { + let nnz = self.row_pointers[self.nrow] as usize; + &mut self.values[..nnz] } - nnz as usize } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { - use super::CsrMatrix; + use super::NumCsrMatrix; use crate::{CooMatrix, Samples, Storage, Symmetry}; - use russell_lab::{vec_approx_eq, Matrix, Vector}; - use std::fs; + use num_complex::Complex64; + use russell_lab::{complex_vec_approx_eq, cpx, vec_approx_eq, ComplexVector, Matrix, Vector}; #[test] fn new_captures_errors() { assert_eq!( - CsrMatrix::new(0, 1, vec![0], vec![], vec![], None).err(), + NumCsrMatrix::::new(0, 1, vec![0], vec![], vec![], None).err(), Some("nrow must be ≥ 1") ); assert_eq!( - CsrMatrix::new(1, 0, vec![0], vec![], vec![], None).err(), + NumCsrMatrix::::new(1, 0, vec![0], vec![], vec![], None).err(), Some("ncol must be ≥ 1") ); assert_eq!( - CsrMatrix::new(1, 1, vec![0], vec![], vec![], None).err(), + NumCsrMatrix::::new(1, 1, vec![0], vec![], vec![], None).err(), Some("row_pointers.len() must be = nrow + 1") ); assert_eq!( - CsrMatrix::new(1, 1, vec![0, 0], vec![], vec![], None).err(), + NumCsrMatrix::::new(1, 1, vec![0, 0], vec![], vec![], None).err(), Some("nnz = row_pointers[nrow] must be ≥ 1") ); assert_eq!( - CsrMatrix::new(1, 1, vec![0, 1], vec![], vec![], None).err(), + NumCsrMatrix::::new(1, 1, vec![0, 1], vec![], vec![], None).err(), Some("col_indices.len() must be ≥ nnz") ); assert_eq!( - CsrMatrix::new(1, 1, vec![0, 1], vec![0], vec![], None).err(), + NumCsrMatrix::::new(1, 1, vec![0, 1], vec![0], vec![], None).err(), Some("values.len() must be ≥ nnz") ); assert_eq!( - CsrMatrix::new(1, 1, vec![-1, 1], vec![0], vec![0.0], None).err(), + NumCsrMatrix::::new(1, 1, vec![-1, 1], vec![0], vec![0.0], None).err(), Some("row pointers must be ≥ 0") ); assert_eq!( - CsrMatrix::new(1, 1, vec![2, 1], vec![0], vec![0.0], None).err(), + NumCsrMatrix::::new(1, 1, vec![2, 1], vec![0], vec![0.0], None).err(), Some("row pointers must be sorted in ascending order") ); assert_eq!( - CsrMatrix::new(1, 1, vec![0, 1], vec![-1], vec![0.0], None).err(), + NumCsrMatrix::::new(1, 1, vec![0, 1], vec![-1], vec![0.0], None).err(), Some("column indices must be ≥ 0") ); assert_eq!( - CsrMatrix::new(1, 1, vec![0, 1], vec![2], vec![0.0], None).err(), + NumCsrMatrix::::new(1, 1, vec![0, 1], vec![2], vec![0.0], None).err(), Some("column indices must be < ncol") ); // ┌ ┐ @@ -891,16 +868,16 @@ mod tests { let col_indices = vec![1, 0]; // << incorrect, should be [0, 1] let row_pointers = vec![0, 2]; assert_eq!( - CsrMatrix::new(1, 2, row_pointers, col_indices, values, None).err(), + NumCsrMatrix::::new(1, 2, row_pointers, col_indices, values, None).err(), Some("column indices must be sorted in ascending order (within their row)") ); } #[test] fn new_works() { - let (_, _, csr_correct, _) = Samples::rectangular_1x2(false, false, false); - let csr = CsrMatrix::new(1, 2, vec![0, 2], vec![0, 1], vec![10.0, 20.0], None).unwrap(); - assert_eq!(csr.symmetry, None); + let (_, _, csr_correct, _) = Samples::rectangular_1x2(false, false); + let csr = NumCsrMatrix::::new(1, 2, vec![0, 2], vec![0, 1], vec![10.0, 20.0], None).unwrap(); + assert_eq!(csr.symmetry, Symmetry::No); assert_eq!(csr.nrow, 1); assert_eq!(csr.ncol, 2); assert_eq!(&csr.row_pointers, &csr_correct.row_pointers); @@ -910,8 +887,11 @@ mod tests { #[test] fn from_coo_captures_errors() { - let coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); - assert_eq!(CsrMatrix::from_coo(&coo).err(), Some("COO to CSR requires nnz > 0")); + let coo = CooMatrix::new(1, 1, 1, None).unwrap(); + assert_eq!( + NumCsrMatrix::::from_coo(&coo).err(), + Some("COO to CSR requires nnz > 0") + ); } #[test] @@ -925,81 +905,78 @@ mod tests { // ┌ ┐ // │ 123 │ // └ ┘ - Samples::tiny_1x1(false), - Samples::tiny_1x1(true), + Samples::tiny_1x1(), // 1 . 2 // . 0 3 // 4 5 6 - Samples::unsymmetric_3x3(false, false, false), - Samples::unsymmetric_3x3(false, true, false), - Samples::unsymmetric_3x3(false, false, true), - Samples::unsymmetric_3x3(false, true, true), - Samples::unsymmetric_3x3(true, false, false), - Samples::unsymmetric_3x3(true, true, false), - Samples::unsymmetric_3x3(true, false, true), - Samples::unsymmetric_3x3(true, true, true), + Samples::unsymmetric_3x3(false, false), + Samples::unsymmetric_3x3(false, true), + Samples::unsymmetric_3x3(true, false), + Samples::unsymmetric_3x3(true, true), // 2 3 . . . // 3 . 4 . 6 // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - Samples::umfpack_unsymmetric_5x5(false), - Samples::umfpack_unsymmetric_5x5(true), + Samples::umfpack_unsymmetric_5x5(), // 1 -1 . -3 . // -2 5 . . . // . . 4 6 4 // -4 . 2 7 . // . 8 . . -5 - Samples::mkl_unsymmetric_5x5(false), - Samples::mkl_unsymmetric_5x5(true), + Samples::mkl_unsymmetric_5x5(), // 1 2 . . . // 3 4 . . . // . . 5 6 . // . . 7 8 . // . . . . 9 - Samples::block_unsymmetric_5x5(false, false, false), - Samples::block_unsymmetric_5x5(false, true, false), - Samples::block_unsymmetric_5x5(false, false, true), - Samples::block_unsymmetric_5x5(false, true, true), - Samples::block_unsymmetric_5x5(true, false, false), - Samples::block_unsymmetric_5x5(true, true, false), - Samples::block_unsymmetric_5x5(true, false, true), - Samples::block_unsymmetric_5x5(true, true, true), + Samples::block_unsymmetric_5x5(false, false), + Samples::block_unsymmetric_5x5(false, true), + Samples::block_unsymmetric_5x5(true, false), + Samples::block_unsymmetric_5x5(true, true), // 9 1.5 6 0.75 3 // 1.5 0.5 . . . // 6 . 12 . . // 0.75 . . 0.625 . // 3 . . . 16 - Samples::mkl_positive_definite_5x5_lower(false), - Samples::mkl_positive_definite_5x5_lower(true), - Samples::mkl_positive_definite_5x5_upper(false), - Samples::mkl_positive_definite_5x5_upper(true), - Samples::mkl_symmetric_5x5_lower(false, false, false), - Samples::mkl_symmetric_5x5_lower(false, true, false), - Samples::mkl_symmetric_5x5_lower(false, false, true), - Samples::mkl_symmetric_5x5_lower(false, true, true), - Samples::mkl_symmetric_5x5_lower(true, false, false), - Samples::mkl_symmetric_5x5_lower(true, true, false), - Samples::mkl_symmetric_5x5_lower(true, false, true), - Samples::mkl_symmetric_5x5_lower(true, true, true), - Samples::mkl_symmetric_5x5_upper(false, false, false), - Samples::mkl_symmetric_5x5_upper(false, true, false), - Samples::mkl_symmetric_5x5_upper(false, false, true), - Samples::mkl_symmetric_5x5_upper(false, true, true), - Samples::mkl_symmetric_5x5_upper(true, false, false), - Samples::mkl_symmetric_5x5_upper(true, true, false), - Samples::mkl_symmetric_5x5_upper(true, false, true), - Samples::mkl_symmetric_5x5_upper(true, true, true), - Samples::mkl_symmetric_5x5_full(false), - Samples::mkl_symmetric_5x5_full(true), + Samples::mkl_positive_definite_5x5_lower(), + Samples::mkl_positive_definite_5x5_upper(), + Samples::mkl_symmetric_5x5_lower(false, false), + Samples::mkl_symmetric_5x5_lower(false, true), + Samples::mkl_symmetric_5x5_lower(true, false), + Samples::mkl_symmetric_5x5_lower(true, true), + Samples::mkl_symmetric_5x5_upper(false, false), + Samples::mkl_symmetric_5x5_upper(false, true), + Samples::mkl_symmetric_5x5_upper(true, false), + Samples::mkl_symmetric_5x5_upper(true, true), + Samples::mkl_symmetric_5x5_full(), + // ┌ ┐ + // │ 10 20 │ + // └ ┘ + Samples::rectangular_1x2(false, false), + Samples::rectangular_1x2(false, true), + Samples::rectangular_1x2(true, false), + Samples::rectangular_1x2(true, true), // ┌ ┐ // │ 1 . 3 . 5 . 7 │ // └ ┘ Samples::rectangular_1x7(), + // ┌ ┐ + // │ . │ + // │ 2 │ + // │ . │ + // │ 4 │ + // │ . │ + // │ 6 │ + // │ . │ + // └ ┘ Samples::rectangular_7x1(), + // 5 -2 . 1 + // 10 -4 . 2 + // 15 -6 . 3 Samples::rectangular_3x4(), ] { - let csr = CsrMatrix::from_coo(&coo).unwrap(); + let csr = NumCsrMatrix::::from_coo(&coo).unwrap(); assert_eq!(&csr.row_pointers, &csr_correct.row_pointers); let nnz = csr.row_pointers[csr.nrow] as usize; assert_eq!(&csr.col_indices[0..nnz], &csr_correct.col_indices); @@ -1007,22 +984,33 @@ mod tests { } } + #[test] + fn debug_conversion() { + let (coo, _, csr_correct, _) = Samples::umfpack_unsymmetric_5x5(); + let csr = NumCsrMatrix::::from_coo(&coo).unwrap(); + assert_eq!(&csr.row_pointers, &csr_correct.row_pointers); + let nnz = csr.row_pointers[csr.nrow] as usize; + assert_eq!(&csr.col_indices[0..nnz], &csr_correct.col_indices); + vec_approx_eq(&csr.values[0..nnz], &csr_correct.values, 1e-15); + } + #[test] #[rustfmt::skip] fn update_from_coo_captures_errors() { - let (coo, _, _, _) = Samples::rectangular_1x2(false, false, false); - let mut csr = CsrMatrix::from_coo(&coo).unwrap(); - let sym = Some(Symmetry::General(Storage::Lower)); - assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: sym, nrow: 1, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0], one_based: false }).err(), Some("coo.symmetry must be equal to csr.symmetry")); - assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: None, nrow: 2, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0], one_based: false }).err(), Some("coo.nrow must be equal to csr.nrow")); - assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: None, nrow: 1, ncol: 1, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0], one_based: false }).err(), Some("coo.ncol must be equal to csr.ncol")); - assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: None, nrow: 1, ncol: 2, nnz: 3, max_nnz: 3, indices_i: vec![0,0,0], indices_j: vec![0,0,0], values: vec![0.0,0.0,0.0], one_based: false }).err(), Some("coo.nnz must be equal to nnz(dup) = self.col_indices.len() = csr.values.len()")); + let (coo, _, _, _) = Samples::rectangular_1x2(false, false); + let mut csr = NumCsrMatrix::::from_coo(&coo).unwrap(); + let yes = Symmetry::General(Storage::Lower); + let no = Symmetry::No; + assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: yes, nrow: 1, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0] }).err(), Some("coo.symmetry must be equal to csr.symmetry")); + assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: no, nrow: 2, ncol: 2, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0] }).err(), Some("coo.nrow must be equal to csr.nrow")); + assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: no, nrow: 1, ncol: 1, nnz: 1, max_nnz: 1, indices_i: vec![0], indices_j: vec![0], values: vec![0.0] }).err(), Some("coo.ncol must be equal to csr.ncol")); + assert_eq!(csr.update_from_coo(&CooMatrix { symmetry: no, nrow: 1, ncol: 2, nnz: 3, max_nnz: 3, indices_i: vec![0,0,0], indices_j: vec![0,0,0], values: vec![0.0,0.0,0.0] }).err(), Some("coo.nnz must be equal to nnz(dup) = self.col_indices.len() = csr.values.len()")); } #[test] fn update_from_coo_again_works() { - let (coo, _, csr_correct, _) = Samples::umfpack_unsymmetric_5x5(false); - let mut csr = CsrMatrix::from_coo(&coo).unwrap(); + let (coo, _, csr_correct, _) = Samples::umfpack_unsymmetric_5x5(); + let mut csr = NumCsrMatrix::::from_coo(&coo).unwrap(); assert_eq!(&csr.row_pointers, &csr_correct.row_pointers); let nnz = csr.row_pointers[csr.nrow] as usize; assert_eq!(&csr.col_indices[0..nnz], &csr_correct.col_indices); @@ -1042,46 +1030,62 @@ mod tests { // ┌ ┐ // │ 123 │ // └ ┘ - Samples::tiny_1x1(IGNORED), + Samples::tiny_1x1(), // 1 . 2 // . 0 3 // 4 5 6 - Samples::unsymmetric_3x3(IGNORED, IGNORED, IGNORED), + Samples::unsymmetric_3x3(IGNORED, IGNORED), // 2 3 . . . // 3 . 4 . 6 // . -1 -3 2 . // . . 1 . . // . 4 2 . 1 - Samples::umfpack_unsymmetric_5x5(IGNORED), + Samples::umfpack_unsymmetric_5x5(), // 1 -1 . -3 . // -2 5 . . . // . . 4 6 4 // -4 . 2 7 . // . 8 . . -5 - Samples::mkl_unsymmetric_5x5(IGNORED), + Samples::mkl_unsymmetric_5x5(), // 1 2 . . . // 3 4 . . . // . . 5 6 . // . . 7 8 . // . . . . 9 - Samples::block_unsymmetric_5x5(IGNORED, IGNORED, IGNORED), + Samples::block_unsymmetric_5x5(IGNORED, IGNORED), // 9 1.5 6 0.75 3 // 1.5 0.5 . . . // 6 . 12 . . // 0.75 . . 0.625 . // 3 . . . 16 - Samples::mkl_positive_definite_5x5_lower(IGNORED), - Samples::mkl_symmetric_5x5_lower(IGNORED, IGNORED, IGNORED), - Samples::mkl_symmetric_5x5_upper(IGNORED, IGNORED, IGNORED), - Samples::mkl_symmetric_5x5_full(IGNORED), + Samples::mkl_positive_definite_5x5_lower(), + Samples::mkl_symmetric_5x5_lower(IGNORED, IGNORED), + Samples::mkl_symmetric_5x5_upper(IGNORED, IGNORED), + Samples::mkl_symmetric_5x5_full(), + // ┌ ┐ + // │ 10 20 │ + // └ ┘ + Samples::rectangular_1x2(IGNORED, IGNORED), // ┌ ┐ // │ 1 . 3 . 5 . 7 │ // └ ┘ Samples::rectangular_1x7(), + // ┌ ┐ + // │ . │ + // │ 2 │ + // │ . │ + // │ 4 │ + // │ . │ + // │ 6 │ + // │ . │ + // └ ┘ Samples::rectangular_7x1(), + // 5 -2 . 1 + // 10 -4 . 2 + // 15 -6 . 3 Samples::rectangular_3x4(), ] { - let csr = CsrMatrix::from_csc(&csc).unwrap(); + let csr = NumCsrMatrix::::from_csc(&csc).unwrap(); assert_eq!(&csr.row_pointers, &csr_correct.row_pointers); assert_eq!(&csr.col_indices, &csr_correct.col_indices); vec_approx_eq(&csr.values, &csr_correct.values, 1e-15); @@ -1090,15 +1094,7 @@ mod tests { #[test] fn to_matrix_fails_on_wrong_dims() { - // 10.0 20.0 - let csr = CsrMatrix { - symmetry: None, - nrow: 1, - ncol: 2, - row_pointers: vec![0, 2], - col_indices: vec![0, 1], - values: vec![10.0, 20.0], - }; + let (_, _, csr, _) = Samples::rectangular_1x2(false, false); let mut a_3x1 = Matrix::new(3, 1); let mut a_1x3 = Matrix::new(1, 3); assert_eq!(csr.to_dense(&mut a_3x1), Err("wrong matrix dimensions")); @@ -1107,15 +1103,8 @@ mod tests { #[test] fn to_matrix_and_as_matrix_work() { - // 10.0 20.0 << (1 x 2) matrix - let csr = CsrMatrix { - symmetry: None, - nrow: 1, - ncol: 2, - row_pointers: vec![0, 2], - col_indices: vec![0, 1], - values: vec![10.0, 20.0], - }; + // 1 x 2 matrix + let (_, _, csr, _) = Samples::rectangular_1x2(false, false); let mut a = Matrix::new(1, 2); csr.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ @@ -1123,32 +1112,8 @@ mod tests { └ ┘"; assert_eq!(format!("{}", a), correct); - let csr = CsrMatrix { - symmetry: None, - nrow: 5, - ncol: 5, - row_pointers: vec![0, 2, 5, 8, 9, 12], - col_indices: vec![ - // p - 0, 1, // i = 0, count = 0, 1 - 0, 2, 4, // i = 1, count = 2, 3, 4 - 1, 2, 3, // i = 2, count = 5, 6, 7 - 2, // i = 3, count = 8 - 1, 2, 4, // i = 4, count = 9, 10, 11 - // count = 12 - ], - values: vec![ - // p - 2.0, 3.0, // i = 0, count = 0, 1 - 3.0, 4.0, 6.0, // i = 1, count = 2, 3, 4 - -1.0, -3.0, 2.0, // i = 2, count = 5, 6, 7 - 1.0, // i = 3, count = 8 - 4.0, 2.0, 1.0, // i = 4, count = 9, 10, 11 - // count = 12 - ], - }; - - // covert to dense + // 5 x 5 matrix + let (_, _, csr, _) = Samples::umfpack_unsymmetric_5x5(); let mut a = Matrix::new(5, 5); csr.to_dense(&mut a).unwrap(); let correct = "┌ ┐\n\ @@ -1170,14 +1135,7 @@ mod tests { #[test] fn as_matrix_upper_works() { - let csr = CsrMatrix { - symmetry: Some(Symmetry::General(Storage::Upper)), - nrow: 5, - ncol: 5, - row_pointers: vec![0, 5, 6, 7, 8, 9], - col_indices: vec![0, 1, 2, 3, 4, 1, 2, 3, 4], - values: vec![9.0, 1.5, 6.0, 0.75, 3.0, 0.5, 12.0, 0.625, 16.0], - }; + let (_, _, csr, _) = Samples::mkl_symmetric_5x5_upper(false, false); let a = csr.as_dense(); let correct = "┌ ┐\n\ │ 9 1.5 6 0.75 3 │\n\ @@ -1191,14 +1149,7 @@ mod tests { #[test] fn as_matrix_lower_works() { - let csr = CsrMatrix { - symmetry: Some(Symmetry::General(Storage::Lower)), - nrow: 5, - ncol: 5, - row_pointers: vec![0, 1, 3, 5, 7, 9], - col_indices: vec![0, 0, 1, 0, 2, 0, 3, 0, 4], - values: vec![9.0, 1.5, 0.5, 6.0, 12.0, 0.75, 0.625, 3.0, 16.0], - }; + let (_, _, csr, _) = Samples::mkl_symmetric_5x5_lower(false, false); let a = csr.as_dense(); let correct = "┌ ┐\n\ │ 9 1.5 6 0.75 3 │\n\ @@ -1210,6 +1161,17 @@ mod tests { assert_eq!(format!("{}", a), correct); } + #[test] + fn mat_vec_mul_captures_errors() { + let (_, _, csr, _) = Samples::rectangular_3x4(); + let u = Vector::new(3); + let mut v = Vector::new(csr.nrow); + assert_eq!(csr.mat_vec_mul(&mut v, 2.0, &u).err(), Some("u vector is incompatible")); + let u = Vector::new(4); + let mut v = Vector::new(2); + assert_eq!(csr.mat_vec_mul(&mut v, 2.0, &u).err(), Some("v vector is incompatible")); + } + #[test] fn mat_vec_mul_works() { // 5 -2 . 1 @@ -1218,29 +1180,77 @@ mod tests { let (_, _, csr, _) = Samples::rectangular_3x4(); let u = Vector::from(&[1.0, 3.0, 8.0, 5.0]); let mut v = Vector::new(csr.nrow); - csr.mat_vec_mul(&mut v, 1.0, &u).unwrap(); - let correct = &[4.0, 8.0, 12.0]; + csr.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + let correct = &[8.0, 16.0, 24.0]; vec_approx_eq(v.as_data(), correct, 1e-15); // call mat_vec_mul again to make sure the vector is filled with zeros before the sum - csr.mat_vec_mul(&mut v, 1.0, &u).unwrap(); + csr.mat_vec_mul(&mut v, 2.0, &u).unwrap(); vec_approx_eq(v.as_data(), correct, 1e-15); } + #[test] + fn mat_vec_mul_symmetric_lower_works() { + let (_, _, csr, _) = Samples::mkl_symmetric_5x5_lower(false, false); + let u = Vector::from(&[1.0, 2.0, 3.0, 4.0, 5.0]); + let mut v = Vector::new(5); + csr.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + vec_approx_eq(v.as_data(), &[96.0, 5.0, 84.0, 6.5, 166.0], 1e-15); + // another test + let (_, _, csr, _) = Samples::lower_symmetric_5x5(); + let u = Vector::from(&[-629.0 / 98.0, 237.0 / 49.0, -53.0 / 49.0, 62.0 / 49.0, 23.0 / 14.0]); + let mut v = Vector::new(5); + csr.mat_vec_mul(&mut v, 2.0, &u).unwrap(); + vec_approx_eq(v.as_data(), &[-4.0, 8.0, 6.0, -10.0, 2.0], 1e-14); + } + + #[test] + fn mat_vec_mul_complex_works() { + // 4+4i . 2+2i + // . 1 3+3i + // . 5+5i 1+1i + // 1 . . + let (_, _, csr, _) = Samples::complex_rectangular_4x3(); + let u = ComplexVector::from(&[cpx!(1.0, 1.0), cpx!(3.0, 1.0), cpx!(5.0, -1.0)]); + let mut v = ComplexVector::new(csr.nrow); + csr.mat_vec_mul(&mut v, cpx!(2.0, 4.0), &u).unwrap(); + let correct = &[ + cpx!(-40.0, 80.0), + cpx!(-10.0, 110.0), + cpx!(-64.0, 112.0), + cpx!(-2.0, 6.0), + ]; + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + // call mat_vec_mul again to make sure the vector is filled with zeros before the sum + csr.mat_vec_mul(&mut v, cpx!(2.0, 4.0), &u).unwrap(); + complex_vec_approx_eq(v.as_data(), correct, 1e-15); + } + #[test] fn getters_are_correct() { - let (_, _, csr, _) = Samples::rectangular_1x2(false, false, false); - assert_eq!(csr.get_info(), (1, 2, 2, None)); + let (_, _, csr, _) = Samples::rectangular_1x2(false, false); + assert_eq!(csr.get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(csr.get_row_pointers(), &[0, 2]); assert_eq!(csr.get_col_indices(), &[0, 1]); assert_eq!(csr.get_values(), &[10.0, 20.0]); - - let mut csr = CsrMatrix { - symmetry: None, + // with duplicates + let (coo, _, _, _) = Samples::rectangular_1x2(false, false); + let csr = NumCsrMatrix::::from_coo(&coo).unwrap(); + assert_eq!(csr.get_info(), (1, 2, 2, Symmetry::No)); + assert_eq!(csr.get_row_pointers(), &[0, 2]); + assert_eq!(csr.get_col_indices(), &[0, 1]); + assert_eq!(csr.get_values(), &[10.0, 20.0]); + // mutable + let mut csr = NumCsrMatrix:: { + symmetry: Symmetry::No, nrow: 1, ncol: 2, values: vec![10.0, 20.0], col_indices: vec![0, 1], row_pointers: vec![0, 2], + temp_rp: Vec::new(), + temp_rjx: Vec::new(), + temp_rc: Vec::new(), + temp_w: Vec::new(), }; let x = csr.get_values_mut(); x.reverse(); @@ -1248,41 +1258,35 @@ mod tests { } #[test] - fn write_matrix_market_works() { - // 2 3 . . . - // 3 . 4 . 6 - // . -1 -3 2 . - // . . 1 . . - // . 4 2 . 1 - let (_, _, csr, _) = Samples::umfpack_unsymmetric_5x5(false); - let full_path = "/tmp/russell_sparse/test_write_matrix_market_csr.mtx"; - csr.write_matrix_market(full_path, false).unwrap(); - let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); - assert_eq!( - contents, - "%%MatrixMarket matrix coordinate real general\n\ - 5 5 12\n\ - 1 1 2.0\n\ - 1 2 3.0\n\ - 2 1 3.0\n\ - 2 3 4.0\n\ - 2 5 6.0\n\ - 3 2 -1.0\n\ - 3 3 -3.0\n\ - 3 4 2.0\n\ - 4 3 1.0\n\ - 5 2 4.0\n\ - 5 3 2.0\n\ - 5 5 1.0\n" - ); - } - - #[test] - fn clone_works() { - let (_, _, csr, _) = Samples::tiny_1x1(false); + fn derive_methods_work() { + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); + let csr = NumCsrMatrix::::from_coo(&coo).unwrap(); + let nrow = coo.nrow; + let nnz = coo.nnz; // it must be COO nnz because of (removed) duplicates + assert_eq!(csr.temp_rp.len(), nrow + 1); + assert_eq!(csr.temp_rjx.len(), nnz); + assert_eq!(csr.temp_rc.len(), nrow); + assert_eq!(csr.temp_w.len(), nrow); let mut clone = csr.clone(); clone.values[0] *= 2.0; - assert_eq!(csr.values[0], 123.0); - assert_eq!(clone.values[0], 246.0); + assert_eq!(csr.values[0], 2.0); + assert_eq!(clone.values[0], 4.0); + assert!(format!("{:?}", csr).len() > 0); + let json = serde_json::to_string(&csr).unwrap(); + assert_eq!( + json, + r#"{"symmetry":"No","nrow":5,"ncol":5,"row_pointers":[0,2,5,8,9,12],"col_indices":[0,1,0,2,4,1,2,3,2,1,2,4,0],"values":[2.0,3.0,3.0,4.0,6.0,-1.0,-3.0,2.0,1.0,4.0,2.0,1.0,0.0]}"# + ); + let from_json: NumCsrMatrix = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json.symmetry, csr.symmetry); + assert_eq!(from_json.nrow, csr.nrow); + assert_eq!(from_json.ncol, csr.ncol); + assert_eq!(from_json.row_pointers, csr.row_pointers); + assert_eq!(from_json.col_indices, csr.col_indices); + assert_eq!(from_json.values, csr.values); + assert_eq!(from_json.temp_rp.len(), 0); + assert_eq!(from_json.temp_rjx.len(), 0); + assert_eq!(from_json.temp_rc.len(), 0); + assert_eq!(from_json.temp_w.len(), 0); } } diff --git a/russell_sparse/src/enums.rs b/russell_sparse/src/enums.rs index e900cf8a..4eafff13 100644 --- a/russell_sparse/src/enums.rs +++ b/russell_sparse/src/enums.rs @@ -1,7 +1,8 @@ use crate::StrError; +use serde::{Deserialize, Serialize}; /// Specifies the underlying library that does all the magic -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum Genie { /// Selects MUMPS (multi-frontal massively parallel sparse direct) solver /// @@ -20,7 +21,7 @@ pub enum Genie { } /// Specifies how the matrix components are stored -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum Storage { /// Lower triangular storage for symmetric matrix (e.g., for MUMPS) Lower, @@ -33,8 +34,11 @@ pub enum Storage { } /// Specifies the type of matrix symmetry -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum Symmetry { + /// Unknown symmetry (possibly unsymmetric) + No, + /// General symmetric General(Storage), @@ -45,7 +49,7 @@ pub enum Symmetry { /// Holds options to handle a MatrixMarket when the matrix is specified as being symmetric /// /// **Note:** This is ignored if not the matrix is not specified as symmetric. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum MMsymOption { /// Leave the storage as lower triangular (if symmetric) /// @@ -73,7 +77,7 @@ pub enum MMsymOption { } /// Ordering option -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum Ordering { /// Ordering using the approximate minimum degree Amd, @@ -107,7 +111,7 @@ pub enum Ordering { } /// Scaling option -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] pub enum Scaling { /// Automatic scaling method selection Auto, @@ -185,104 +189,91 @@ impl Genie { } /// Returns the solver's required symmetry/storage configuration - pub fn symmetry(&self, symmetric: bool, positive_definite: bool) -> Option { + pub fn symmetry(&self, symmetric: bool, positive_definite: bool) -> Symmetry { let storage = self.storage(); if symmetric || positive_definite { if positive_definite { - Some(Symmetry::PositiveDefinite(storage)) + Symmetry::PositiveDefinite(storage) } else { - Some(Symmetry::General(storage)) + Symmetry::General(storage) } } else { - None - } - } - - /// Returns whether the sparse matrix indices must be one-based or not - pub fn one_based(&self) -> bool { - match self { - Genie::Mumps => true, - Genie::Umfpack => false, - Genie::IntelDss => false, + Symmetry::No } } } impl Symmetry { /// Returns a new general symmetry flag with lower storage - pub fn new_general_lower() -> Option { - Some(Symmetry::General(Storage::Lower)) + pub fn new_general_lower() -> Self { + Symmetry::General(Storage::Lower) } /// Returns a new general symmetry flag with upper storage - pub fn new_general_upper() -> Option { - Some(Symmetry::General(Storage::Upper)) + pub fn new_general_upper() -> Self { + Symmetry::General(Storage::Upper) } /// Returns a new general symmetry flag with full storage - pub fn new_general_full() -> Option { - Some(Symmetry::General(Storage::Full)) + pub fn new_general_full() -> Self { + Symmetry::General(Storage::Full) } /// Returns a new positive-definite symmetry flag with lower storage - pub fn new_pos_def_lower() -> Option { - Some(Symmetry::PositiveDefinite(Storage::Lower)) + pub fn new_pos_def_lower() -> Self { + Symmetry::PositiveDefinite(Storage::Lower) } /// Returns a new positive-definite symmetry flag with upper storage - pub fn new_pos_def_upper() -> Option { - Some(Symmetry::PositiveDefinite(Storage::Upper)) + pub fn new_pos_def_upper() -> Self { + Symmetry::PositiveDefinite(Storage::Upper) } /// Returns a new positive-definite symmetry flag with full storage - pub fn new_pos_def_full() -> Option { - Some(Symmetry::PositiveDefinite(Storage::Full)) + pub fn new_pos_def_full() -> Self { + Symmetry::PositiveDefinite(Storage::Full) } /// Returns which type of storage is used, if symmetric - pub fn storage(symmetry: Option) -> Storage { - match symmetry { - Some(sym) => { - if sym.lower() { - Storage::Lower - } else if sym.upper() { - Storage::Upper - } else { - Storage::Full - } - } - None => Storage::Full, + pub fn storage(symmetry: Symmetry) -> Storage { + if symmetry.lower() { + Storage::Lower + } else if symmetry.upper() { + Storage::Upper + } else { + Storage::Full } } /// Returns true if the storage is triangular (lower or upper) pub fn triangular(&self) -> bool { match self { - Self::General(storage) => *storage != Storage::Full, - Self::PositiveDefinite(storage) => *storage != Storage::Full, + Symmetry::No => false, + Symmetry::General(storage) => *storage != Storage::Full, + Symmetry::PositiveDefinite(storage) => *storage != Storage::Full, } } /// Returns true if the storage is lower triangular pub fn lower(&self) -> bool { match self { - Self::General(storage) => *storage == Storage::Lower, - Self::PositiveDefinite(storage) => *storage == Storage::Lower, + Symmetry::No => false, + Symmetry::General(storage) => *storage == Storage::Lower, + Symmetry::PositiveDefinite(storage) => *storage == Storage::Lower, } } /// Returns true if the storage is upper triangular pub fn upper(&self) -> bool { match self { - Self::General(storage) => *storage == Storage::Upper, - Self::PositiveDefinite(storage) => *storage == Storage::Upper, + Symmetry::No => false, + Symmetry::General(storage) => *storage == Storage::Upper, + Symmetry::PositiveDefinite(storage) => *storage == Storage::Upper, } } /// Returns status flags indicating the type of symmetry, if any /// - /// Returns `(general_symmetric, positive_definite)` - /// /// # Input /// /// * `must_be_lower` -- makes sure that the storage is Lower @@ -290,11 +281,14 @@ impl Symmetry { /// /// # Output /// + /// Returns `(general_symmetric, positive_definite)` where: + /// /// * `general_symmetric` -- 1 if true, 0 otherwise /// * `positive_definite` -- 1 if true, 0 otherwise pub fn status(&self, must_be_lower: bool, must_be_upper: bool) -> Result<(i32, i32), StrError> { match self { - Self::General(storage) => { + Symmetry::No => Ok((0, 0)), + Symmetry::General(storage) => { if must_be_lower && *storage != Storage::Lower { return Err("if the matrix is general symmetric, the required storage is lower triangular"); } @@ -303,7 +297,7 @@ impl Symmetry { } Ok((1, 0)) } - Self::PositiveDefinite(storage) => { + Symmetry::PositiveDefinite(storage) => { if must_be_lower && *storage != Storage::Lower { return Err("if the matrix is positive-definite, the required storage is lower triangular"); } @@ -385,13 +379,16 @@ mod tests { use super::*; #[test] - fn clone_copy_and_debug_work() { + fn derive_methods_work() { let genie = Genie::Mumps; let copy = genie; let clone = genie.clone(); assert_eq!(format!("{:?}", genie), "Mumps"); assert_eq!(copy, Genie::Mumps); assert_eq!(clone, Genie::Mumps); + let json = serde_json::to_string(&genie).unwrap(); + let from_json: Genie = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, genie); let storage = Storage::Full; let copy = storage; @@ -399,6 +396,9 @@ mod tests { assert_eq!(format!("{:?}", storage), "Full"); assert_eq!(copy, Storage::Full); assert_eq!(clone, Storage::Full); + let json = serde_json::to_string(&storage).unwrap(); + let from_json: Storage = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, storage); let symmetry = Symmetry::PositiveDefinite(Storage::Lower); let copy = symmetry; @@ -406,6 +406,9 @@ mod tests { assert_eq!(format!("{:?}", symmetry), "PositiveDefinite(Lower)"); assert_eq!(copy, Symmetry::PositiveDefinite(Storage::Lower)); assert_eq!(clone, Symmetry::PositiveDefinite(Storage::Lower)); + let json = serde_json::to_string(&symmetry).unwrap(); + let from_json: Symmetry = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, symmetry); let handling = MMsymOption::LeaveAsLower; let copy = handling; @@ -413,6 +416,9 @@ mod tests { assert_eq!(format!("{:?}", handling), "LeaveAsLower"); assert_eq!(copy, MMsymOption::LeaveAsLower); assert_eq!(clone, MMsymOption::LeaveAsLower); + let json = serde_json::to_string(&handling).unwrap(); + let from_json: MMsymOption = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, handling); let ordering = Ordering::Amd; let copy = ordering; @@ -420,6 +426,9 @@ mod tests { assert_eq!(format!("{:?}", ordering), "Amd"); assert_eq!(format!("{:?}", copy), "Amd"); assert_eq!(format!("{:?}", clone), "Amd"); + let json = serde_json::to_string(&ordering).unwrap(); + let from_json: Ordering = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, ordering); let scaling = Scaling::Column; let copy = scaling; @@ -427,6 +436,9 @@ mod tests { assert_eq!(format!("{:?}", scaling), "Column"); assert_eq!(format!("{:?}", copy), "Column"); assert_eq!(format!("{:?}", clone), "Column"); + let json = serde_json::to_string(&scaling).unwrap(); + let from_json: Scaling = serde_json::from_str(&json).unwrap(); + assert_eq!(from_json, scaling); } #[test] @@ -496,40 +508,37 @@ mod tests { let u = Storage::Upper; let f = Storage::Full; - let gl = Some(Symmetry::General(l)); - let gu = Some(Symmetry::General(u)); - let gf = Some(Symmetry::General(f)); + let gl = Symmetry::General(l); + let gu = Symmetry::General(u); + let gf = Symmetry::General(f); - let pl = Some(Symmetry::PositiveDefinite(l)); - let pu = Some(Symmetry::PositiveDefinite(u)); - let pf = Some(Symmetry::PositiveDefinite(f)); + let pl = Symmetry::PositiveDefinite(l); + let pu = Symmetry::PositiveDefinite(u); + let pf = Symmetry::PositiveDefinite(f); let genie = Genie::Mumps; assert_eq!(genie.to_string(), "mumps"); assert_eq!(genie.storage(), l); - assert_eq!(genie.symmetry(false, false), None); + assert_eq!(genie.symmetry(false, false), Symmetry::No); assert_eq!(genie.symmetry(true, false), gl); assert_eq!(genie.symmetry(false, true), pl); assert_eq!(genie.symmetry(true, true), pl); - assert_eq!(genie.one_based(), true); let genie = Genie::Umfpack; assert_eq!(genie.to_string(), "umfpack"); assert_eq!(genie.storage(), f); - assert_eq!(genie.symmetry(false, false), None); + assert_eq!(genie.symmetry(false, false), Symmetry::No); assert_eq!(genie.symmetry(true, false), gf); assert_eq!(genie.symmetry(false, true), pf); assert_eq!(genie.symmetry(true, true), pf); - assert_eq!(genie.one_based(), false); let genie = Genie::IntelDss; assert_eq!(genie.to_string(), "inteldss"); assert_eq!(genie.storage(), u); - assert_eq!(genie.symmetry(false, false), None); + assert_eq!(genie.symmetry(false, false), Symmetry::No); assert_eq!(genie.symmetry(true, false), gu); assert_eq!(genie.symmetry(false, true), pu); assert_eq!(genie.symmetry(true, true), pu); - assert_eq!(genie.one_based(), false); } #[test] @@ -546,12 +555,12 @@ mod tests { let pu = Symmetry::PositiveDefinite(u); let pf = Symmetry::PositiveDefinite(f); - assert_eq!(Some(gl), Symmetry::new_general_lower()); - assert_eq!(Some(gu), Symmetry::new_general_upper()); - assert_eq!(Some(gf), Symmetry::new_general_full()); - assert_eq!(Symmetry::storage(Some(gl)), Storage::Lower); - assert_eq!(Symmetry::storage(Some(gu)), Storage::Upper); - assert_eq!(Symmetry::storage(Some(gf)), Storage::Full); + assert_eq!(Symmetry::new_general_lower(), gl); + assert_eq!(Symmetry::new_general_upper(), gu); + assert_eq!(Symmetry::new_general_full(), gf); + assert_eq!(Symmetry::storage(gl), Storage::Lower); + assert_eq!(Symmetry::storage(gu), Storage::Upper); + assert_eq!(Symmetry::storage(gf), Storage::Full); assert_eq!(gl.triangular(), true); assert_eq!(gu.triangular(), true); assert_eq!(gf.triangular(), false); @@ -576,12 +585,12 @@ mod tests { Err("if the matrix is general symmetric, the required storage is lower triangular") ); - assert_eq!(Some(pl), Symmetry::new_pos_def_lower()); - assert_eq!(Some(pu), Symmetry::new_pos_def_upper()); - assert_eq!(Some(pf), Symmetry::new_pos_def_full()); - assert_eq!(Symmetry::storage(Some(pl)), Storage::Lower); - assert_eq!(Symmetry::storage(Some(pu)), Storage::Upper); - assert_eq!(Symmetry::storage(Some(pf)), Storage::Full); + assert_eq!(Symmetry::new_pos_def_lower(), pl); + assert_eq!(Symmetry::new_pos_def_upper(), pu); + assert_eq!(Symmetry::new_pos_def_full(), pf); + assert_eq!(Symmetry::storage(pl), Storage::Lower); + assert_eq!(Symmetry::storage(pu), Storage::Upper); + assert_eq!(Symmetry::storage(pf), Storage::Full); assert_eq!(pl.triangular(), true); assert_eq!(pu.triangular(), true); assert_eq!(pf.triangular(), false); @@ -606,6 +615,6 @@ mod tests { Err("if the matrix is positive-definite, the required storage is lower triangular") ); - assert_eq!(Symmetry::storage(None), Storage::Full); + assert_eq!(Symmetry::storage(Symmetry::No), Storage::Full); } } diff --git a/russell_sparse/src/lib.rs b/russell_sparse/src/lib.rs index 6ba324de..ba282ab8 100644 --- a/russell_sparse/src/lib.rs +++ b/russell_sparse/src/lib.rs @@ -6,15 +6,20 @@ //! //! # Introduction //! -//! We have three storage formats for sparse matrices: +//! This crate implements three storage formats for sparse matrices: //! -//! * [CooMatrix] (COO) -- COOrdinates matrix, also known as a sparse triplet. -//! * [CscMatrix] (CSC) -- Compressed Sparse Column matrix -//! * [CsrMatrix] (CSR) -- Compressed Sparse Row matrix +//! * [NumCooMatrix] (COO) -- COOrdinates matrix, also known as a sparse triplet. +//! * [NumCscMatrix] (CSC) -- Compressed Sparse Column matrix +//! * [NumCsrMatrix] (CSR) -- Compressed Sparse Row matrix //! -//! Additionally, to unify the handling of the above sparse matrix data structures, we have: +//! Additionally, to unify the handling of the above data structures, this implements: //! -//! * [SparseMatrix] -- Either a COO, CSC, or CSR matrix +//! * [NumSparseMatrix] -- Either a COO, CSC, or CSR matrix. We recommend using `NumSparseMatrix` solely, if possible. +//! +//! For convenience, this crate defines the following type aliases for Real and Complex matrices (with double precision): +//! +//! * [CooMatrix], [CscMatrix], [CsrMatrix], [SparseMatrix] -- For real numbers represented by `f64` +//! * [ComplexCooMatrix], [ComplexCscMatrix], [ComplexCsrMatrix], [ComplexSparseMatrix] -- For complex numbers represented by [num_complex::Complex64] //! //! The COO matrix is the best when we need to update the values of the matrix because it has easy access to the triples (i, j, aij). For instance, the repetitive access is the primary use case for codes based on the finite element method (FEM) for approximating partial differential equations. Moreover, the COO matrix allows storing duplicate entries; for example, the triple `(0, 0, 123.0)` can be stored as two triples `(0, 0, 100.0)` and `(0, 0, 23.0)`. Again, this is the primary need for FEM codes because of the so-called assembly process where elements add to the same positions in the "global stiffness" matrix. Nonetheless, the duplicate entries must be summed up at some stage for the linear solver (e.g., MUMPS, UMFPACK, and Intel DSS). These linear solvers also use the more memory-efficient storage formats CSC and CSR. The following is the default input for these solvers: //! @@ -57,7 +62,7 @@ //! //! The linear solvers have numerous configuration parameters; however, we can use the default parameters initially. The configuration parameters are collected in the [LinSolParams] structures, which is an input to the [LinSolTrait::factorize()]. The parameters include options such as [Ordering] and [Scaling]. //! -//! This library also provides functions to read and write Matrix Market files containing (huge) sparse matrices that can be used in performance benchmarking or other studies. The [read_matrix_market()] function reads a Matrix Market file and returns a [CooMatrix]. To write a Matrix Market file, we can use the function [SparseMatrix::write_matrix_market()], which automatically converts COO to CSC or COO to CSR, also performing the sum of duplicates. The `write_matrix_market` can also writs an SMAT file (almost like the Matrix Market format) without the header and with zero-based indices. The SMAT file can be given to the fantastic [Vismatrix](https://github.com/cpmech/vismatrix) tool to visualize the sparse matrix structure and values interactively; see the example below. +//! This library also provides functions to read and write Matrix Market files containing (huge) sparse matrices that can be used in performance benchmarking or other studies. The [read_matrix_market()] function reads a Matrix Market file and returns a [CooMatrix]. To write a Matrix Market file, we can use the function [csc_write_matrix_market()] (and similar), which automatically converts COO to CSC or COO to CSR, also performing the sum of duplicates. The `write_matrix_market` can also writs an SMAT file (almost like the Matrix Market format) without the header and with zero-based indices. The SMAT file can be given to the fantastic [Vismatrix](https://github.com/cpmech/vismatrix) tool to visualize the sparse matrix structure and values interactively; see the example below. //! //! ![doc-example-vismatrix](https://raw.githubusercontent.com/cpmech/russell/main/russell_sparse/data/figures/doc-example-vismatrix.png) //! @@ -77,7 +82,7 @@ //! // │ 4 5 6 │ but should be saved for Intel DSS //! // └ ┘ //! let (nrow, ncol, nnz) = (3, 3, 6); -//! let mut coo = CooMatrix::new(nrow, ncol, nnz, None, false)?; +//! let mut coo = CooMatrix::new(nrow, ncol, nnz, None)?; //! coo.put(0, 0, 1.0)?; //! coo.put(0, 2, 2.0)?; //! coo.put(1, 2, 3.0)?; @@ -127,7 +132,7 @@ //! let mut solver = LinSolver::new(Genie::Umfpack)?; //! //! // allocate the coefficient matrix -//! let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; +//! let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; //! coo.put(0, 0, 0.2)?; //! coo.put(0, 1, 0.2)?; //! coo.put(1, 0, 0.5)?; @@ -186,7 +191,7 @@ //! // . -1 -3 2 . //! // . . 1 . . //! // . 4 2 . 1 -//! let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; +//! let mut coo = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; //! coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate //! coo.put(0, 0, 1.0)?; // << (0, 0, a00/2) duplicate //! coo.put(1, 0, 3.0)?; @@ -224,21 +229,27 @@ //! // analysis //! let mut stats = StatsLinSol::new(); //! umfpack.update_stats(&mut stats); -//! let (mx, ex) = (stats.determinant.mantissa, stats.determinant.exponent); +//! let (mx, ex) = (stats.determinant.mantissa_real, stats.determinant.exponent); //! println!("det(a) = {:?}", mx * f64::powf(10.0, ex)); //! println!("rcond = {:?}", stats.output.umfpack_rcond_estimate); //! Ok(()) //! } //! ``` -/// Defines a type alias for the error type as a static string +/// Defines the error output as a static string pub type StrError = &'static str; +mod aliases; mod auxiliary_and_constants; +mod complex_coo_matrix; +mod complex_lin_solver; +mod complex_solver_mumps; +mod complex_solver_umfpack; mod coo_matrix; mod csc_matrix; mod csr_matrix; mod enums; +mod lin_sol_params; mod lin_solver; pub mod prelude; mod read_matrix_market; @@ -249,11 +260,17 @@ mod solver_umfpack; mod sparse_matrix; mod stats_lin_sol; mod verify_lin_sys; +mod write_matrix_market; +pub use crate::aliases::*; use crate::auxiliary_and_constants::*; +pub use crate::complex_lin_solver::*; +pub use crate::complex_solver_mumps::*; +pub use crate::complex_solver_umfpack::*; pub use crate::coo_matrix::*; pub use crate::csc_matrix::*; pub use crate::csr_matrix::*; pub use crate::enums::*; +pub use crate::lin_sol_params::*; pub use crate::lin_solver::*; pub use crate::read_matrix_market::*; pub use crate::samples::*; @@ -263,6 +280,7 @@ pub use crate::solver_umfpack::*; pub use crate::sparse_matrix::*; pub use crate::stats_lin_sol::*; pub use crate::verify_lin_sys::*; +pub use crate::write_matrix_market::*; // run code from README file #[cfg(doctest)] diff --git a/russell_sparse/src/lin_sol_params.rs b/russell_sparse/src/lin_sol_params.rs new file mode 100644 index 00000000..2ae61b84 --- /dev/null +++ b/russell_sparse/src/lin_sol_params.rs @@ -0,0 +1,99 @@ +use super::{Ordering, Scaling}; + +/// Defines the configuration parameters for the linear system solver +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct LinSolParams { + /// Defines the symmetric permutation (ordering) + pub ordering: Ordering, + + /// Defines the scaling strategy + pub scaling: Scaling, + + /// Requests that the determinant be computed + /// + /// **Note:** The determinant will be available after `factorize` + pub compute_determinant: bool, + + /// Requests that the error estimates be computed + /// + /// **Note:** Will need to use the `actual` solver to access the results. + pub compute_error_estimates: bool, + + /// Requests that condition numbers be computed + /// + /// **Note:** Will need to use the `actual` solver to access the results. + pub compute_condition_numbers: bool, + + /// Sets the % increase in the estimated working space (MUMPS only) + /// + /// **Note:** The default (recommended) value is 100 (%) + pub mumps_pct_inc_workspace: usize, + + /// Sets the max size of the working memory in mega bytes (MUMPS only) + /// + /// **Note:** Set this value to 0 for an automatic configuration + pub mumps_max_work_memory: usize, + + /// Defines the number of threads for MUMPS + /// + /// **Note:** Set this value to 0 to allow an automatic detection + pub mumps_num_threads: usize, + + /// Overrides the prevention of number-of-threads issue with OpenBLAS (not recommended) + pub mumps_override_prevent_nt_issue_with_openblas: bool, + + /// Enforces the unsymmetric strategy, even for symmetric matrices (not recommended; UMFPACK only) + pub umfpack_enforce_unsymmetric_strategy: bool, + + /// Show additional messages + pub verbose: bool, +} + +impl LinSolParams { + /// Allocates a new instance with default values + pub fn new() -> Self { + LinSolParams { + ordering: Ordering::Auto, + scaling: Scaling::Auto, + compute_determinant: false, + compute_error_estimates: false, + compute_condition_numbers: false, + mumps_pct_inc_workspace: 100, + mumps_max_work_memory: 0, + mumps_num_threads: 0, + mumps_override_prevent_nt_issue_with_openblas: false, + umfpack_enforce_unsymmetric_strategy: false, + verbose: false, + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::LinSolParams; + use crate::{Ordering, Scaling}; + + #[test] + fn clone_copy_and_debug_work() { + let params = LinSolParams::new(); + let copy = params; + let clone = params.clone(); + assert!(format!("{:?}", params).len() > 0); + assert_eq!(copy, params); + assert_eq!(clone, params); + } + + #[test] + fn lin_sol_params_new_works() { + let params = LinSolParams::new(); + assert_eq!(params.ordering, Ordering::Auto); + assert_eq!(params.scaling, Scaling::Auto); + assert_eq!(params.compute_determinant, false); + assert_eq!(params.mumps_pct_inc_workspace, 100); + assert_eq!(params.mumps_max_work_memory, 0); + assert_eq!(params.mumps_num_threads, 0); + assert!(!params.umfpack_enforce_unsymmetric_strategy); + } +} diff --git a/russell_sparse/src/lin_solver.rs b/russell_sparse/src/lin_solver.rs index ea45e931..f0d6a8db 100644 --- a/russell_sparse/src/lin_solver.rs +++ b/russell_sparse/src/lin_solver.rs @@ -1,75 +1,7 @@ -use super::{Genie, Ordering, Scaling, SolverIntelDSS, SolverMUMPS, SolverUMFPACK, SparseMatrix, StatsLinSol}; +use super::{Genie, LinSolParams, SolverIntelDSS, SolverMUMPS, SolverUMFPACK, SparseMatrix, StatsLinSol}; use crate::StrError; use russell_lab::Vector; -/// Defines the configuration parameters for the linear system solver -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct LinSolParams { - /// Defines the symmetric permutation (ordering) - pub ordering: Ordering, - - /// Defines the scaling strategy - pub scaling: Scaling, - - /// Requests that the determinant be computed - /// - /// **Note:** The determinant will be available after `factorize` - pub compute_determinant: bool, - - /// Requests that the error estimates be computed - /// - /// **Note:** Will need to use the `actual` solver to access the results. - pub compute_error_estimates: bool, - - /// Requests that condition numbers be computed - /// - /// **Note:** Will need to use the `actual` solver to access the results. - pub compute_condition_numbers: bool, - - /// Sets the % increase in the estimated working space (MUMPS only) - /// - /// **Note:** The default (recommended) value is 100 (%) - pub mumps_pct_inc_workspace: usize, - - /// Sets the max size of the working memory in mega bytes (MUMPS only) - /// - /// **Note:** Set this value to 0 for an automatic configuration - pub mumps_max_work_memory: usize, - - /// Defines the number of threads for MUMPS - /// - /// **Note:** Set this value to 0 to allow an automatic detection - pub mumps_num_threads: usize, - - /// Overrides the prevention of number-of-threads issue with OpenBLAS (not recommended) - pub mumps_override_prevent_nt_issue_with_openblas: bool, - - /// Enforces the unsymmetric strategy, even for symmetric matrices (not recommended; UMFPACK only) - pub umfpack_enforce_unsymmetric_strategy: bool, - - /// Show additional messages - pub verbose: bool, -} - -impl LinSolParams { - /// Allocates a new instance with default values - pub fn new() -> Self { - LinSolParams { - ordering: Ordering::Auto, - scaling: Scaling::Auto, - compute_determinant: false, - compute_error_estimates: false, - compute_condition_numbers: false, - mumps_pct_inc_workspace: 100, - mumps_max_work_memory: 0, - mumps_num_threads: 0, - mumps_override_prevent_nt_issue_with_openblas: false, - umfpack_enforce_unsymmetric_strategy: false, - verbose: false, - } - } -} - /// Defines a unified interface for linear system solvers pub trait LinSolTrait { /// Performs the factorization (and analysis/initialization if needed) @@ -119,7 +51,7 @@ pub trait LinSolTrait { /// Unifies the access to linear system solvers pub struct LinSolver<'a> { /// Holds the actual implementation - pub actual: Box, + pub actual: Box, } impl<'a> LinSolver<'a> { @@ -129,7 +61,7 @@ impl<'a> LinSolver<'a> { /// /// * `genie` -- the actual implementation that does all the magic pub fn new(genie: Genie) -> Result { - let actual: Box = match genie { + let actual: Box = match genie { Genie::Mumps => Box::new(SolverMUMPS::new()?), Genie::Umfpack => Box::new(SolverUMFPACK::new()?), Genie::IntelDss => Box::new(SolverIntelDSS::new()?), @@ -179,7 +111,7 @@ impl<'a> LinSolver<'a> { /// let nnz = 5; // number of non-zero values /// /// // allocate the coefficient matrix - /// let mut mat = SparseMatrix::new_coo(ndim, ndim, nnz, None, false)?; + /// let mut mat = SparseMatrix::new_coo(ndim, ndim, nnz, None)?; /// mat.put(0, 0, 0.2)?; /// mat.put(0, 1, 0.2)?; /// mat.put(1, 0, 0.5)?; @@ -225,31 +157,10 @@ impl<'a> LinSolver<'a> { #[cfg(test)] mod tests { - use super::{LinSolParams, LinSolver}; - use crate::{Genie, Ordering, Samples, Scaling, SparseMatrix}; + use super::LinSolver; + use crate::{Genie, Samples, SparseMatrix}; use russell_lab::{vec_approx_eq, Vector}; - - #[test] - fn clone_copy_and_debug_work() { - let params = LinSolParams::new(); - let copy = params; - let clone = params.clone(); - assert!(format!("{:?}", params).len() > 0); - assert_eq!(copy, params); - assert_eq!(clone, params); - } - - #[test] - fn lin_sol_params_new_works() { - let params = LinSolParams::new(); - assert_eq!(params.ordering, Ordering::Auto); - assert_eq!(params.scaling, Scaling::Auto); - assert_eq!(params.compute_determinant, false); - assert_eq!(params.mumps_pct_inc_workspace, 100); - assert_eq!(params.mumps_max_work_memory, 0); - assert_eq!(params.mumps_num_threads, 0); - assert!(!params.umfpack_enforce_unsymmetric_strategy); - } + use serial_test::serial; #[test] fn lin_solver_new_works() { @@ -261,8 +172,20 @@ mod tests { } #[test] - fn lin_solver_compute_works() { - let (coo, _, _, _) = Samples::mkl_symmetric_5x5_full(false); + #[serial] + fn lin_solver_compute_works_mumps() { + let (coo, _, _, _) = Samples::mkl_symmetric_5x5_lower(true, false); + let mut mat = SparseMatrix::from_coo(coo); + let mut x = Vector::new(5); + let rhs = Vector::from(&[1.0, 2.0, 3.0, 4.0, 5.0]); + LinSolver::compute(Genie::Mumps, &mut x, &mut mat, &rhs, None).unwrap(); + let x_correct = vec![-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; + vec_approx_eq(x.as_data(), &x_correct, 1e-10); + } + + #[test] + fn lin_solver_compute_works_umfpack() { + let (coo, _, _, _) = Samples::mkl_symmetric_5x5_full(); let mut mat = SparseMatrix::from_coo(coo); let mut x = Vector::new(5); let rhs = Vector::from(&[1.0, 2.0, 3.0, 4.0, 5.0]); diff --git a/russell_sparse/src/prelude.rs b/russell_sparse/src/prelude.rs index 57bf767d..5328c48d 100644 --- a/russell_sparse/src/prelude.rs +++ b/russell_sparse/src/prelude.rs @@ -3,15 +3,20 @@ //! You may write `use russell_sparse::prelude::*` in your code and obtain //! access to commonly used functionality. -pub use crate::coo_matrix::CooMatrix; -pub use crate::csc_matrix::CscMatrix; -pub use crate::csr_matrix::CsrMatrix; +pub use crate::aliases::*; +pub use crate::complex_lin_solver::*; +pub use crate::complex_solver_mumps::ComplexSolverMUMPS; +pub use crate::complex_solver_umfpack::ComplexSolverUMFPACK; +pub use crate::coo_matrix::NumCooMatrix; +pub use crate::csc_matrix::NumCscMatrix; +pub use crate::csr_matrix::NumCsrMatrix; pub use crate::enums::*; +pub use crate::lin_sol_params::LinSolParams; pub use crate::lin_solver::*; pub use crate::read_matrix_market; pub use crate::solver_intel_dss::SolverIntelDSS; pub use crate::solver_mumps::SolverMUMPS; pub use crate::solver_umfpack::SolverUMFPACK; -pub use crate::sparse_matrix::SparseMatrix; +pub use crate::sparse_matrix::NumSparseMatrix; pub use crate::stats_lin_sol::StatsLinSol; pub use crate::verify_lin_sys::VerifyLinSys; diff --git a/russell_sparse/src/read_matrix_market.rs b/russell_sparse/src/read_matrix_market.rs index d349ee0e..f277433c 100644 --- a/russell_sparse/src/read_matrix_market.rs +++ b/russell_sparse/src/read_matrix_market.rs @@ -167,7 +167,6 @@ impl MatrixMarketData { /// /// * `full_path` -- may be a String, &str, or Path /// * `symmetric_handling` -- Options to handle symmetric matrices -/// * `one_based` -- Use one-based indices; e.g., for MUMPS or other FORTRAN routines /// /// # Output /// @@ -265,12 +264,12 @@ impl MatrixMarketData { /// /// fn main() -> Result<(), StrError> { /// let name = "./data/matrix_market/ok_simple_general.mtx"; -/// let coo = read_matrix_market(name, MMsymOption::LeaveAsLower, false)?; +/// let coo = read_matrix_market(name, MMsymOption::LeaveAsLower)?; /// let (nrow, ncol, nnz, symmetry) = coo.get_info(); /// assert_eq!(nrow, 3); /// assert_eq!(ncol, 3); /// assert_eq!(nnz, 5); -/// assert_eq!(symmetry, None); +/// assert_eq!(symmetry, Symmetry::No); /// let a = coo.as_dense(); /// let correct = "┌ ┐\n\ /// │ 1 2 0 │\n\ @@ -304,12 +303,12 @@ impl MatrixMarketData { /// /// fn main() -> Result<(), StrError> { /// let name = "./data/matrix_market/ok_simple_symmetric.mtx"; -/// let coo = read_matrix_market(name, MMsymOption::LeaveAsLower, false)?; +/// let coo = read_matrix_market(name, MMsymOption::LeaveAsLower)?; /// let (nrow, ncol, nnz, symmetry) = coo.get_info(); /// assert_eq!(nrow, 3); /// assert_eq!(ncol, 3); /// assert_eq!(nnz, 4); -/// assert_eq!(symmetry, Some(Symmetry::General(Storage::Lower))); +/// assert_eq!(symmetry, Symmetry::General(Storage::Lower)); /// let a = coo.as_dense(); /// let correct = "┌ ┐\n\ /// │ 1 2 0 │\n\ @@ -320,11 +319,7 @@ impl MatrixMarketData { /// Ok(()) /// } /// ``` -pub fn read_matrix_market

( - full_path: &P, - symmetric_handling: MMsymOption, - one_based: bool, -) -> Result +pub fn read_matrix_market

(full_path: &P, symmetric_handling: MMsymOption) -> Result where P: AsRef + ?Sized, { @@ -374,7 +369,7 @@ where } // allocate triplet - let mut coo = CooMatrix::new(data.m as usize, data.n as usize, max as usize, symmetry, one_based).unwrap(); + let mut coo = CooMatrix::new(data.m as usize, data.n as usize, max as usize, symmetry).unwrap(); // read and parse triples loop { @@ -537,30 +532,29 @@ mod tests { #[test] fn read_matrix_market_handle_wrong_files() { let h = MMsymOption::LeaveAsLower; - let o = false; - assert_eq!(read_matrix_market("__wrong__", h, o).err(), Some("cannot open file")); + assert_eq!(read_matrix_market("__wrong__", h).err(), Some("cannot open file")); assert_eq!( - read_matrix_market("./data/matrix_market/bad_empty_file.mtx", h, o).err(), + read_matrix_market("./data/matrix_market/bad_empty_file.mtx", h).err(), Some("file is empty") ); assert_eq!( - read_matrix_market("./data/matrix_market/bad_wrong_header.mtx", h, o).err(), + read_matrix_market("./data/matrix_market/bad_wrong_header.mtx", h).err(), Some("after %%MatrixMarket, the first option must be \"matrix\"") ); assert_eq!( - read_matrix_market("./data/matrix_market/bad_wrong_dims.mtx", h, o).err(), + read_matrix_market("./data/matrix_market/bad_wrong_dims.mtx", h).err(), Some("found invalid (zero or negative) dimensions") ); assert_eq!( - read_matrix_market("./data/matrix_market/bad_missing_data.mtx", h, o).err(), + read_matrix_market("./data/matrix_market/bad_missing_data.mtx", h).err(), Some("not all triples (i, j, aij) have been found") ); assert_eq!( - read_matrix_market("./data/matrix_market/bad_many_lines.mtx", h, o).err(), + read_matrix_market("./data/matrix_market/bad_many_lines.mtx", h).err(), Some("there are more (i, j, aij) triples than specified") ); assert_eq!( - read_matrix_market("./data/matrix_market/bad_symmetric_rectangular.mtx", h,o).err(), + read_matrix_market("./data/matrix_market/bad_symmetric_rectangular.mtx", h).err(), Some("MatrixMarket data is invalid: the number of rows must be equal the number of columns for symmetric matrices") ); } @@ -568,10 +562,9 @@ mod tests { #[test] fn read_matrix_market_works() { let h = MMsymOption::LeaveAsLower; - let o = false; let filepath = "./data/matrix_market/ok_general.mtx".to_string(); - let coo = read_matrix_market(&filepath, h, o).unwrap(); - assert_eq!(coo.symmetry, None); + let coo = read_matrix_market(&filepath, h).unwrap(); + assert_eq!(coo.symmetry, Symmetry::No); assert_eq!((coo.nrow, coo.ncol, coo.nnz, coo.max_nnz), (5, 5, 12, 12)); assert_eq!(coo.indices_i, &[0, 1, 0, 2, 4, 1, 2, 3, 4, 2, 1, 4]); assert_eq!(coo.indices_j, &[0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4]); @@ -584,10 +577,9 @@ mod tests { #[test] fn read_matrix_market_symmetric_lower_works() { let h = MMsymOption::LeaveAsLower; - let o = false; let filepath = "./data/matrix_market/ok_symmetric.mtx".to_string(); - let coo = read_matrix_market(&filepath, h, o).unwrap(); - assert_eq!(coo.symmetry, Some(Symmetry::General(Storage::Lower))); + let coo = read_matrix_market(&filepath, h).unwrap(); + assert_eq!(coo.symmetry, Symmetry::General(Storage::Lower)); assert_eq!((coo.nrow, coo.ncol, coo.nnz, coo.max_nnz), (5, 5, 15, 15)); assert_eq!(coo.indices_i, &[0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4]); assert_eq!(coo.indices_j, &[0, 1, 2, 3, 4, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3]); @@ -600,10 +592,9 @@ mod tests { #[test] fn read_matrix_market_symmetric_upper_works() { let h = MMsymOption::SwapToUpper; - let o = false; let filepath = "./data/matrix_market/ok_symmetric.mtx".to_string(); - let coo = read_matrix_market(&filepath, h, o).unwrap(); - assert_eq!(coo.symmetry, Some(Symmetry::General(Storage::Upper))); + let coo = read_matrix_market(&filepath, h).unwrap(); + assert_eq!(coo.symmetry, Symmetry::General(Storage::Upper)); assert_eq!((coo.nrow, coo.ncol, coo.nnz, coo.max_nnz), (5, 5, 15, 15)); assert_eq!(coo.indices_i, &[0, 1, 2, 3, 4, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3]); assert_eq!(coo.indices_j, &[0, 1, 2, 3, 4, 1, 2, 3, 4, 2, 3, 4, 3, 4, 4]); @@ -616,10 +607,9 @@ mod tests { #[test] fn read_matrix_market_symmetric_to_full_works() { let h = MMsymOption::MakeItFull; - let o = false; let filepath = "./data/matrix_market/ok_symmetric_small.mtx".to_string(); - let coo = read_matrix_market(&filepath, h, o).unwrap(); - assert_eq!(coo.symmetry, Some(Symmetry::General(Storage::Full))); + let coo = read_matrix_market(&filepath, h).unwrap(); + assert_eq!(coo.symmetry, Symmetry::General(Storage::Full)); assert_eq!((coo.nrow, coo.ncol, coo.nnz, coo.max_nnz), (5, 5, 11, 14)); assert_eq!(coo.indices_i, &[0, 1, 0, 2, 1, 3, 2, 3, 4, 1, 4, 0, 0, 0]); assert_eq!(coo.indices_j, &[0, 0, 1, 1, 2, 2, 3, 3, 1, 4, 4, 0, 0, 0]); diff --git a/russell_sparse/src/samples.rs b/russell_sparse/src/samples.rs index 156368f9..146c8eca 100644 --- a/russell_sparse/src/samples.rs +++ b/russell_sparse/src/samples.rs @@ -1,4 +1,7 @@ +use crate::{ComplexCooMatrix, ComplexCscMatrix, ComplexCsrMatrix}; use crate::{CooMatrix, CscMatrix, CsrMatrix, Storage, Symmetry}; +use num_complex::Complex64; +use russell_lab::cpx; const PLACEHOLDER: f64 = f64::MAX; @@ -8,19 +11,17 @@ pub struct Samples {} impl Samples { /// Returns a (1 x 1) matrix /// - /// Note: the last return value is not the determinant, but a PLACEHOLDER - /// /// ```text /// ┌ ┐ /// │ 123 │ /// └ ┘ /// ``` - pub fn tiny_1x1(one_based: bool) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + pub fn tiny_1x1() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { let sym = None; let nrow = 1; let ncol = 1; let max_nnz = 1; - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); coo.put(0, 0, 123.0).unwrap(); // CSC matrix let col_pointers = vec![0, 1]; @@ -35,6 +36,245 @@ impl Samples { (coo, csc, csr, 123.0) } + /// Returns a (1 x 1) matrix (complex version) + /// + /// ```text + /// ┌ ┐ + /// │ 12+3i │ + /// └ ┘ + /// ``` + pub fn complex_tiny_1x1() -> (ComplexCooMatrix, ComplexCscMatrix, ComplexCsrMatrix, Complex64) { + let sym = None; + let nrow = 1; + let ncol = 1; + let max_nnz = 1; + let mut coo = ComplexCooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); + coo.put(0, 0, cpx!(12.0, 3.0)).unwrap(); + // CSC matrix + let col_pointers = vec![0, 1]; + let row_indices = vec![0]; + let values = vec![cpx!(12.0, 3.0)]; + let csc = ComplexCscMatrix::new(nrow, ncol, col_pointers, row_indices, values, sym).unwrap(); + // CSR matrix + let row_pointers = vec![0, 1]; + let col_indices = vec![0]; + let values = vec![cpx!(12.0, 3.0)]; + let csr = ComplexCsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); + (coo, csc, csr, cpx!(12.0, 3.0)) + } + + /// Returns a (3 x 3) positive definite matrix + /// + /// ```text + /// 2 -1 2 sym + /// -1 2 -1 => -1 2 + /// -1 2 -1 2 + /// ``` + pub fn positive_definite_3x3() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + let (nrow, ncol, nnz) = (3, 3, 6); + let sym = Some(Symmetry::PositiveDefinite(Storage::Lower)); + let mut coo = CooMatrix::new(nrow, ncol, nnz, sym).unwrap(); + coo.put(1, 0, -0.5).unwrap(); // duplicate + coo.put(0, 0, 2.0).unwrap(); + coo.put(2, 2, 2.0).unwrap(); + coo.put(1, 0, -0.5).unwrap(); // duplicate + coo.put(1, 1, 2.0).unwrap(); + coo.put(2, 1, -1.0).unwrap(); + // CSC matrix + let col_pointers = vec![0, 2, 4, 5]; + let row_indices = vec![ + 0, 1, // j=0, p=(0),1 + 1, 2, // j=1, p=(2),3 + 2, // j=2, p=(4) + ]; // (5) + let values = vec![ + 2.0, -1.0, // j=0, p=(0),1 + 2.0, -1.0, // j=1, p=(2),3 + 2.0, // j=2, p=(4) + ]; // (5) + let csc = CscMatrix::new(nrow, ncol, col_pointers, row_indices, values, sym).unwrap(); + // CSR matrix + let row_pointers = vec![0, 1, 3, 5]; + let col_indices = vec![ + 0, // i=0, p=(0) + 0, 1, // i=1, p=(1),2 + 1, 2, // i=2, p=(3),4 + ]; // (5) + let values = vec![ + 2.0, // i=0, p=(0) + -1.0, 2.0, // i=1, p=(1),2 + -1.0, 2.0, // i=2, p=(3),4 + ]; // (5) + let csr = CsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); + (coo, csc, csr, 4.0) + } + + /// Returns a complex symmetric (3 x 3) matrix (lower storage) + /// + /// ```text + /// 2+1i -1-1i 2+1i sym + /// -1-1i 2+2i -1+1i => -1-1i 2+2i + /// -1+1i 2-1i -1+1i 2-1i + /// ``` + pub fn complex_symmetric_3x3_lower() -> (ComplexCooMatrix, ComplexCscMatrix, ComplexCsrMatrix, Complex64) { + let (nrow, ncol, nnz) = (3, 3, 6); + let sym = Some(Symmetry::General(Storage::Lower)); + let mut coo = ComplexCooMatrix::new(nrow, ncol, nnz, sym).unwrap(); + coo.put(1, 0, cpx!(-0.5, -0.5)).unwrap(); // duplicate + coo.put(0, 0, cpx!(2.0, 1.0)).unwrap(); + coo.put(2, 2, cpx!(2.0, -1.0)).unwrap(); + coo.put(1, 0, cpx!(-0.5, -0.5)).unwrap(); // duplicate + coo.put(1, 1, cpx!(2.0, 2.0)).unwrap(); + coo.put(2, 1, cpx!(-1.0, 1.0)).unwrap(); + // CSC matrix + let col_pointers = vec![0, 2, 4, 5]; + let row_indices = vec![ + 0, 1, // j=0, p=(0),1 + 1, 2, // j=1, p=(2),3 + 2, // j=2, p=(4) + ]; // (5) + #[rustfmt::skip] + let values = vec![ + cpx!(2.0, 1.0), cpx!(-1.0, -1.0), // j=0, p=(0),1 + cpx!(2.0, 2.0), cpx!(-1.0, 1.0), // j=1, p=(2),3 + cpx!(2.0, -1.0), // j=2, p=(4) + ]; // (5) + let csc = ComplexCscMatrix::new(nrow, ncol, col_pointers, row_indices, values, sym).unwrap(); + // CSR matrix + let row_pointers = vec![0, 1, 3, 5]; + let col_indices = vec![ + 0, // i=0, p=(0) + 0, 1, // i=1, p=(1),2 + 1, 2, // i=2, p=(3),4 + ]; // (5) + #[rustfmt::skip] + let values = vec![ + cpx!( 2.0, 1.0), // i=0, p=(0) + cpx!(-1.0, -1.0), cpx!(2.0, 2.0), // i=1, p=(1),2 + cpx!(-1.0, 1.0), cpx!(2.0, -1.0), // i=2, p=(3),4 + ]; // (5) + let csr = ComplexCsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); + (coo, csc, csr, cpx!(6.0, 10.0)) + } + + /// Returns a complex symmetric (3 x 3) matrix (full storage) + /// + /// ```text + /// 2+1i -1-1i + /// -1-1i 2+2i -1+1i + /// -1+1i 2-1i + /// ``` + pub fn complex_symmetric_3x3_full() -> (ComplexCooMatrix, ComplexCscMatrix, ComplexCsrMatrix, Complex64) { + let (nrow, ncol, nnz) = (3, 3, 8); + let sym = Some(Symmetry::General(Storage::Full)); + let mut coo = ComplexCooMatrix::new(nrow, ncol, nnz, sym).unwrap(); + coo.put(1, 0, cpx!(-0.5, -0.5)).unwrap(); // duplicate + coo.put(0, 0, cpx!(2.0, 1.0)).unwrap(); + coo.put(2, 2, cpx!(2.0, -1.0)).unwrap(); + coo.put(1, 0, cpx!(-0.5, -0.5)).unwrap(); // duplicate + coo.put(1, 1, cpx!(2.0, 2.0)).unwrap(); + coo.put(2, 1, cpx!(-1.0, 1.0)).unwrap(); + coo.put(0, 1, cpx!(-1.0, -1.0)).unwrap(); + coo.put(1, 2, cpx!(-1.0, 1.0)).unwrap(); + // CSC matrix + let col_pointers = vec![0, 2, 5, 7]; + let row_indices = vec![ + 0, 1, // j=0, p=(0),1 + 0, 1, 2, // j=1, p=(2),3,4 + 1, 2, // j=2, p=(5),6 + ]; // (7) + #[rustfmt::skip] + let values = vec![ + cpx!( 2.0, 1.0), cpx!(-1.0, -1.0), // j=0, p=(0),1 + cpx!(-1.0, -1.0), cpx!( 2.0, 2.0), cpx!(-1.0, 1.0), // j=1, p=(2),3,4 + cpx!(-1.0, 1.0), cpx!( 2.0, -1.0), // j=2, p=(5),6 + ]; // (7) + let csc = ComplexCscMatrix::new(nrow, ncol, col_pointers, row_indices, values, sym).unwrap(); + // CSR matrix + let row_pointers = vec![0, 2, 5, 7]; + let col_indices = vec![ + 0, 1, // i=0, p=(0),1 + 0, 1, 2, // i=1, p=(2),3,4 + 1, 2, // i=2, p=(5),6 + ]; // (7) + #[rustfmt::skip] + let values = vec![ + cpx!( 2.0, 1.0), cpx!(-1.0, -1.0), // i=0, p=(0),1 + cpx!(-1.0, -1.0), cpx!( 2.0, 2.0), cpx!(-1.0, 1.0), // i=1, p=(2),3,4 + cpx!(-1.0, 1.0), cpx!( 2.0, -1.0), // i=2, p=(5),6 + ]; // (7) + let csr = ComplexCsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); + (coo, csc, csr, cpx!(6.0, 10.0)) + } + + /// Returns a lower symmetric 5 x 5 matrix + /// + /// ```text + /// 2 1 1 3 2 2 + /// 1 2 2 1 1 1 2 sym + /// 1 2 9 1 5 => 1 2 9 + /// 3 1 1 7 1 3 1 1 7 + /// 2 1 5 1 8 2 1 5 1 8 + /// ``` + pub fn lower_symmetric_5x5() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + let (nrow, ncol, nnz) = (5, 5, 18); + let sym = Some(Symmetry::PositiveDefinite(Storage::Lower)); + let mut coo = CooMatrix::new(nrow, ncol, nnz, sym).unwrap(); + coo.put(1, 1, 2.0).unwrap(); + coo.put(4, 2, 2.5).unwrap(); // duplicate + coo.put(2, 2, 9.0).unwrap(); + coo.put(3, 3, 7.0).unwrap(); + coo.put(0, 0, 2.0).unwrap(); + coo.put(4, 4, 5.0).unwrap(); // duplicate + coo.put(2, 0, 1.0).unwrap(); + coo.put(4, 4, 3.0).unwrap(); // duplicate + coo.put(2, 1, 2.0).unwrap(); + coo.put(1, 0, 1.0).unwrap(); + coo.put(3, 0, 3.0).unwrap(); + coo.put(3, 2, 1.0).unwrap(); + coo.put(4, 0, 2.0).unwrap(); + coo.put(3, 1, 0.5).unwrap(); // duplicate + coo.put(3, 1, 0.5).unwrap(); // duplicate + coo.put(4, 1, 1.0).unwrap(); + coo.put(4, 2, 2.5).unwrap(); // duplicate + coo.put(4, 3, 1.0).unwrap(); + // CSC matrix + let col_pointers = vec![0, 5, 9, 12, 14, 15]; + let row_indices = vec![ + 0, 1, 2, 3, 4, // j=0, p=(0),1,2,3,4 + 1, 2, 3, 4, // j=1, p=(5),6,7,8 + 2, 3, 4, // j=2, p=(9),10,11 + 3, 4, // j=3, p=(12),13 + 4, // j=4, p=(14) + ]; // (15) + let values = vec![ + 2.0, 1.0, 1.0, 3.0, 2.0, // j=0, p=(0),1,2,3,4 + 2.0, 2.0, 1.0, 1.0, // j=1, p=(5),6,7,8 + 9.0, 1.0, 5.0, // j=2, p=(9),10,11 + 7.0, 1.0, // j=3, p=(12),13 + 8.0, // j=4, p=(14) + ]; // (15) + let csc = CscMatrix::new(nrow, ncol, col_pointers, row_indices, values, sym).unwrap(); + // CSR matrix + let row_pointers = vec![0, 1, 3, 6, 10, 15]; + let col_indices = vec![ + 0, // i=0, p=(0) + 0, 1, // i=1, p=(1),2 + 0, 1, 2, // i=2, p=(3),4,5 + 0, 1, 2, 3, // i=3, p=(6),7,8,9 + 0, 1, 2, 3, 4, // i=4, p=(10),11,12,13,14 + ]; // (15) + let values = vec![ + 2.0, // i=0, p=(0) + 1.0, 2.0, // i=1, p=(1),2 + 1.0, 2.0, 9.0, // i=2, p=(3),4,5 + 3.0, 1.0, 1.0, 7.0, // i=3, p=(6),7,8,9 + 2.0, 1.0, 5.0, 1.0, 8.0, // i=4, p=(10),11,12,13,14 + ]; // (15) + let csr = CsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); + (coo, csc, csr, 98.0) + } + /// Returns the COO, CSC, and CSR versions of the matrix and its determinant /// /// ```text @@ -59,7 +299,6 @@ impl Samples { /// let x_correct = &[3.0, 3.0, 15]; /// ``` pub fn unsymmetric_3x3( - one_based: bool, shuffle_coo_entries: bool, duplicate_coo_entries: bool, ) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { @@ -67,7 +306,7 @@ impl Samples { let nrow = 3; let ncol = 3; let max_nnz = 10; // more nnz than needed => OK - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); if shuffle_coo_entries { if duplicate_coo_entries { coo.put(0, 2, 2.0).unwrap(); @@ -127,7 +366,7 @@ impl Samples { 1.0, 2.0, // i=0 p=(0),1 0.0, 3.0, // i=1 p=(2),3 4.0, 5.0, 6.0, // i=2 p=(4),5,6 - ]; // p=(7) + ]; // p=(7) let col_indices = vec![ 0, 2, // 1, 2, // @@ -161,25 +400,25 @@ impl Samples { /// ```text /// let x_correct = &[1.0, 2.0, 3.0, 4.0, 5.0]; /// ``` - pub fn umfpack_unsymmetric_5x5(one_based: bool) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + pub fn umfpack_unsymmetric_5x5() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { let sym = None; let nrow = 5; let ncol = 5; let max_nnz = 13; - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) duplicate - coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) duplicate - coo.put(1, 0, 3.0).unwrap(); - coo.put(0, 1, 3.0).unwrap(); coo.put(2, 1, -1.0).unwrap(); + coo.put(1, 0, 3.0).unwrap(); coo.put(4, 1, 4.0).unwrap(); - coo.put(1, 2, 4.0).unwrap(); - coo.put(2, 2, -3.0).unwrap(); + coo.put(4, 4, 1.0).unwrap(); + coo.put(0, 1, 3.0).unwrap(); coo.put(3, 2, 1.0).unwrap(); + coo.put(2, 2, -3.0).unwrap(); + coo.put(0, 0, 1.0).unwrap(); // << (0, 0, a00/2) duplicate coo.put(4, 2, 2.0).unwrap(); coo.put(2, 3, 2.0).unwrap(); coo.put(1, 4, 6.0).unwrap(); - coo.put(4, 4, 1.0).unwrap(); + coo.put(1, 2, 4.0).unwrap(); // CSC matrix let values = vec![ 2.0, 3.0, // j=0, p=( 0),1 @@ -204,7 +443,7 @@ impl Samples { -1.0, -3.0, 2.0, // i=2, p=(5),6,7 1.0, // i=3, p=(8) 4.0, 2.0, 1.0, // i=4, p=(9),10,11 - ]; // p=(12) + ]; // p=(12) let col_indices = vec![ 0, 1, // 0, 2, 4, // @@ -231,11 +470,11 @@ impl Samples { /// /// Reference: /// - pub fn mkl_unsymmetric_5x5(one_based: bool) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + pub fn mkl_unsymmetric_5x5() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { let sym = None; let nrow = 5; let ncol = 5; - let mut coo = CooMatrix::new(nrow, ncol, 13, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, 13, sym).unwrap(); coo.put(2, 4, 4.0).unwrap(); coo.put(4, 1, 8.0).unwrap(); coo.put(0, 1, -1.0).unwrap(); @@ -298,7 +537,6 @@ impl Samples { /// . . . . 9 /// ``` pub fn block_unsymmetric_5x5( - one_based: bool, shuffle_coo_entries: bool, duplicate_coo_entries: bool, ) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { @@ -306,7 +544,7 @@ impl Samples { let nrow = 5; let ncol = 5; let max_nnz = 11; // more nnz than needed => OK - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); if shuffle_coo_entries { if duplicate_coo_entries { coo.put(4, 4, 9.0).unwrap(); @@ -416,11 +654,11 @@ impl Samples { /// ```text /// x_correct = vec![-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; /// ``` - pub fn mkl_positive_definite_5x5_lower(one_based: bool) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + pub fn mkl_positive_definite_5x5_lower() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { let sym = Some(Symmetry::PositiveDefinite(Storage::Lower)); let nrow = 5; let ncol = 5; - let mut coo = CooMatrix::new(nrow, ncol, 9, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, 9, sym).unwrap(); coo.put(0, 0, 9.0).unwrap(); coo.put(1, 1, 0.5).unwrap(); coo.put(2, 2, 12.0).unwrap(); @@ -490,11 +728,11 @@ impl Samples { /// ```text /// x_correct = vec![-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; /// ``` - pub fn mkl_positive_definite_5x5_upper(one_based: bool) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + pub fn mkl_positive_definite_5x5_upper() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { let sym = Some(Symmetry::PositiveDefinite(Storage::Upper)); let nrow = 5; let ncol = 5; - let mut coo = CooMatrix::new(nrow, ncol, 9, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, 9, sym).unwrap(); coo.put(0, 0, 9.0).unwrap(); coo.put(0, 1, 1.5).unwrap(); coo.put(1, 1, 0.5).unwrap(); @@ -565,7 +803,6 @@ impl Samples { /// x_correct = vec![-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; /// ``` pub fn mkl_symmetric_5x5_lower( - one_based: bool, shuffle_coo_entries: bool, duplicate_coo_entries: bool, ) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { @@ -573,7 +810,7 @@ impl Samples { let nrow = 5; let ncol = 5; let max_nnz = 13; - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); if shuffle_coo_entries { if duplicate_coo_entries { // diagonal @@ -688,7 +925,6 @@ impl Samples { /// x_correct = vec![-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; /// ``` pub fn mkl_symmetric_5x5_upper( - one_based: bool, shuffle_coo_entries: bool, duplicate_coo_entries: bool, ) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { @@ -696,7 +932,7 @@ impl Samples { let nrow = 5; let ncol = 5; let max_nnz = 15; - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); if shuffle_coo_entries { if duplicate_coo_entries { coo.put(0, 0, 6.0).unwrap(); // << duplicate @@ -809,12 +1045,12 @@ impl Samples { /// ```text /// x_correct = vec![-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; /// ``` - pub fn mkl_symmetric_5x5_full(one_based: bool) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { + pub fn mkl_symmetric_5x5_full() -> (CooMatrix, CscMatrix, CsrMatrix, f64) { let sym = Some(Symmetry::General(Storage::Full)); let nrow = 5; let ncol = 5; let max_nnz = 13; - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); coo.put(0, 0, 9.0).unwrap(); coo.put(0, 1, 1.5).unwrap(); coo.put(0, 2, 6.0).unwrap(); @@ -875,7 +1111,6 @@ impl Samples { /// └ ┘ /// ``` pub fn rectangular_1x2( - one_based: bool, shuffle_coo_entries: bool, duplicate_coo_entries: bool, ) -> (CooMatrix, CscMatrix, CsrMatrix, f64) { @@ -883,7 +1118,7 @@ impl Samples { let nrow = 1; let ncol = 2; let max_nnz = 10; - let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym, one_based).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, max_nnz, sym).unwrap(); if shuffle_coo_entries { if duplicate_coo_entries { coo.put(0, 1, 2.0).unwrap(); @@ -942,7 +1177,7 @@ impl Samples { let sym = None; let nrow = 1; let ncol = 7; - let mut coo = CooMatrix::new(nrow, ncol, 4, sym, false).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, 4, sym).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(0, 2, 3.0).unwrap(); coo.put(0, 4, 5.0).unwrap(); @@ -989,7 +1224,7 @@ impl Samples { let sym = None; let nrow = 7; let ncol = 1; - let mut coo = CooMatrix::new(nrow, ncol, 3, sym, false).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, 3, sym).unwrap(); coo.put(1, 0, 2.0).unwrap(); coo.put(3, 0, 4.0).unwrap(); coo.put(5, 0, 6.0).unwrap(); @@ -1029,7 +1264,7 @@ impl Samples { let sym = None; let nrow = 3; let ncol = 4; - let mut coo = CooMatrix::new(nrow, ncol, 9, sym, false).unwrap(); + let mut coo = CooMatrix::new(nrow, ncol, 9, sym).unwrap(); coo.put(0, 0, 5.0).unwrap(); coo.put(1, 0, 10.0).unwrap(); coo.put(2, 0, 15.0).unwrap(); @@ -1069,6 +1304,61 @@ impl Samples { let csr = CsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); (coo, csc, csr, PLACEHOLDER) } + + /// Returns a (4 x 3) complex rectangular matrix + /// + /// Note: the last return value is not the determinant, but a PLACEHOLDER + /// + /// ```text + /// 4+4i . 2+2i + /// . 1 3+3i + /// . 5+5i 1+1i + /// 1 . . + /// ``` + pub fn complex_rectangular_4x3() -> (ComplexCooMatrix, ComplexCscMatrix, ComplexCsrMatrix, f64) { + let sym = None; + let nrow = 4; + let ncol = 3; + let mut coo = ComplexCooMatrix::new(nrow, ncol, 7, sym).unwrap(); + coo.put(0, 0, cpx!(4.0, 4.0)).unwrap(); + coo.put(0, 2, cpx!(2.0, 2.0)).unwrap(); + coo.put(1, 1, cpx!(1.0, 0.0)).unwrap(); + coo.put(1, 2, cpx!(3.0, 3.0)).unwrap(); + coo.put(2, 1, cpx!(5.0, 5.0)).unwrap(); + coo.put(2, 2, cpx!(1.0, 1.0)).unwrap(); + coo.put(3, 0, cpx!(1.0, 0.0)).unwrap(); + // CSC matrix + let col_pointers = vec![0, 2, 4, 7]; + let row_indices = vec![ + 0, 3, // j=0, p=(0),1 + 1, 2, // j=1, p=(2),3 + 0, 1, 2, // j=2, p=(4),5,6 + ]; // (7) + #[rustfmt::skip] + let values = vec![ + cpx!(4.0,4.0), cpx!(1.0,0.0), // j=0, p=(0),1 + cpx!(1.0,0.0), cpx!(5.0,5.0), // j=1, p=(2),3 + cpx!(2.0,2.0), cpx!(3.0,3.0), cpx!(1.0,1.0), // j=2, p=(4),5,6 + ]; // (7) + let csc = ComplexCscMatrix::new(nrow, ncol, col_pointers, row_indices, values, sym).unwrap(); + // CSR matrix + let row_pointers = vec![0, 2, 4, 6, 7]; + let col_indices = vec![ + 0, 2, // i=0, p=(0),1 + 1, 2, // i=1, p=(2),3 + 1, 2, // i=2, p=(4),5 + 0, // i=3, p=(6) + ]; // (7) + #[rustfmt::skip] + let values = vec![ + cpx!(4.0,4.0), cpx!(2.0,2.0), // i=0, p=(0),1 + cpx!(1.0,0.0), cpx!(3.0,3.0), // i=1, p=(2),3 + cpx!(5.0,5.0), cpx!(1.0,1.0), // i=2, p=(4),5 + cpx!(1.0,0.0), // i=3, p=(6) + ]; // (7) + let csr = ComplexCsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, sym).unwrap(); + (coo, csc, csr, PLACEHOLDER) + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1076,8 +1366,14 @@ impl Samples { #[cfg(test)] mod tests { use super::Samples; - use crate::{CooMatrix, CscMatrix, CsrMatrix}; + use crate::{NumCooMatrix, NumCscMatrix, NumCsrMatrix}; + use num_complex::Complex64; + use num_traits::{Num, NumCast}; use russell_lab::{approx_eq, mat_approx_eq, mat_inverse, Matrix}; + use russell_lab::{complex_approx_eq, complex_mat_approx_eq, complex_mat_inverse, cpx, ComplexMatrix}; + use serde::de::DeserializeOwned; + use serde::Serialize; + use std::ops::{AddAssign, MulAssign}; /// Checks the samples /// @@ -1100,7 +1396,10 @@ mod tests { /// col_indices.len() == nnz /// values.len() == nnz /// ``` - fn check(coo: &CooMatrix, csc: &CscMatrix, csr: &CsrMatrix) { + fn check(coo: &NumCooMatrix, csc: &NumCscMatrix, csr: &NumCsrMatrix) + where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, + { // COO let max_nnz = coo.max_nnz; assert!(coo.nrow >= 1); @@ -1144,15 +1443,89 @@ mod tests { let a = Matrix::from(correct); let mut ai = Matrix::new(1, 1); let correct_det = mat_inverse(&mut ai, &a).unwrap(); - for (coo, csc, csr, det) in [ - Samples::tiny_1x1(false), // - Samples::tiny_1x1(true), // - ] { - let mat = coo.as_dense(); - approx_eq(det, correct_det, 1e-13); - mat_approx_eq(&mat, correct, 1e-15); - check(&coo, &csc, &csr); - } + let (coo, csc, csr, det) = Samples::tiny_1x1(); + approx_eq(det, correct_det, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); + + // ---------------------------------------------------------------------------- + + let correct = &[ + [cpx!(12.0, 3.0)], // + ]; + let a = ComplexMatrix::from(correct); + let mut ai = ComplexMatrix::new(1, 1); + let correct_det = complex_mat_inverse(&mut ai, &a).unwrap(); + let (coo, csc, csr, det) = Samples::complex_tiny_1x1(); + complex_approx_eq(det, correct_det, 1e-15); + complex_mat_approx_eq(&coo.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csc.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); + + // ---------------------------------------------------------------------------- + + let correct = &[ + [2.0, -1.0, 0.0], // + [-1.0, 2.0, -1.0], // + [0.0, -1.0, 2.0], // + ]; + let a = Matrix::from(correct); + let mut ai = Matrix::new(3, 3); + let correct_det = mat_inverse(&mut ai, &a).unwrap(); + let (coo, csc, csr, det) = Samples::positive_definite_3x3(); + approx_eq(det, correct_det, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); + + // ---------------------------------------------------------------------------- + + #[rustfmt::skip] + let correct = &[ + [cpx!( 2.0, 1.0), cpx!(-1.0, -1.0), cpx!( 0.0, 0.0)], + [cpx!(-1.0, -1.0), cpx!( 2.0, 2.0), cpx!(-1.0, 1.0)], + [cpx!( 0.0, 0.0), cpx!(-1.0, 1.0), cpx!( 2.0, -1.0)], + ]; + let a = ComplexMatrix::from(correct); + let mut ai = ComplexMatrix::new(3, 3); + let correct_det = complex_mat_inverse(&mut ai, &a).unwrap(); + // lower + let (coo, csc, csr, det) = Samples::complex_symmetric_3x3_lower(); + complex_approx_eq(det, correct_det, 1e-15); + complex_mat_approx_eq(&coo.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csc.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); + // full + let (coo, csc, csr, det) = Samples::complex_symmetric_3x3_full(); + complex_approx_eq(det, correct_det, 1e-15); + complex_mat_approx_eq(&coo.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csc.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); + + // ---------------------------------------------------------------------------- + + let correct = &[ + [2.0, 1.0, 1.0, 3.0, 2.0], + [1.0, 2.0, 2.0, 1.0, 1.0], + [1.0, 2.0, 9.0, 1.0, 5.0], + [3.0, 1.0, 1.0, 7.0, 1.0], + [2.0, 1.0, 5.0, 1.0, 8.0], + ]; + let a = Matrix::from(correct); + let mut ai = Matrix::new(5, 5); + let correct_det = mat_inverse(&mut ai, &a).unwrap(); + let (coo, csc, csr, det) = Samples::lower_symmetric_5x5(); + approx_eq(det, correct_det, 1e-12); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); // ---------------------------------------------------------------------------- @@ -1165,18 +1538,15 @@ mod tests { let mut ai = Matrix::new(3, 3); let correct_det = mat_inverse(&mut ai, &a).unwrap(); for (coo, csc, csr, det) in [ - Samples::unsymmetric_3x3(false, false, false), - Samples::unsymmetric_3x3(false, true, false), - Samples::unsymmetric_3x3(false, true, false), - Samples::unsymmetric_3x3(false, true, true), - Samples::unsymmetric_3x3(true, false, false), - Samples::unsymmetric_3x3(true, true, false), - Samples::unsymmetric_3x3(true, true, false), - Samples::unsymmetric_3x3(true, true, true), + Samples::unsymmetric_3x3(false, false), + Samples::unsymmetric_3x3(false, true), + Samples::unsymmetric_3x3(true, false), + Samples::unsymmetric_3x3(true, true), ] { - let mat = coo.as_dense(); approx_eq(det, correct_det, 1e-13); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); } @@ -1192,15 +1562,12 @@ mod tests { let a = Matrix::from(correct); let mut ai = Matrix::new(5, 5); let correct_det = mat_inverse(&mut ai, &a).unwrap(); - for (coo, csc, csr, det) in [ - Samples::umfpack_unsymmetric_5x5(false), - Samples::umfpack_unsymmetric_5x5(true), - ] { - let mat = coo.as_dense(); - approx_eq(det, correct_det, 1e-13); - mat_approx_eq(&mat, correct, 1e-15); - check(&coo, &csc, &csr); - } + let (coo, csc, csr, det) = Samples::umfpack_unsymmetric_5x5(); + approx_eq(det, correct_det, 1e-13); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); // ---------------------------------------------------------------------------- @@ -1214,14 +1581,12 @@ mod tests { let a = Matrix::from(correct); let mut ai = Matrix::new(5, 5); let correct_det = mat_inverse(&mut ai, &a).unwrap(); - for (coo, csc, csr, det) in [ - Samples::mkl_unsymmetric_5x5(false), // - ] { - let mat = coo.as_dense(); - approx_eq(det, correct_det, 1e-13); - mat_approx_eq(&mat, correct, 1e-15); - check(&coo, &csc, &csr); - } + let (coo, csc, csr, det) = Samples::mkl_unsymmetric_5x5(); + approx_eq(det, correct_det, 1e-13); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); // ---------------------------------------------------------------------------- @@ -1236,18 +1601,15 @@ mod tests { let mut ai = Matrix::new(5, 5); let correct_det = mat_inverse(&mut ai, &a).unwrap(); for (coo, csc, csr, det) in [ - Samples::block_unsymmetric_5x5(false, false, false), - Samples::block_unsymmetric_5x5(false, true, false), - Samples::block_unsymmetric_5x5(false, false, true), - Samples::block_unsymmetric_5x5(false, true, true), - Samples::block_unsymmetric_5x5(true, false, false), - Samples::block_unsymmetric_5x5(true, true, false), - Samples::block_unsymmetric_5x5(true, false, true), - Samples::block_unsymmetric_5x5(true, true, true), + Samples::block_unsymmetric_5x5(false, false), + Samples::block_unsymmetric_5x5(false, true), + Samples::block_unsymmetric_5x5(true, false), + Samples::block_unsymmetric_5x5(true, true), ] { - let mat = coo.as_dense(); approx_eq(det, correct_det, 1e-13); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); } @@ -1264,32 +1626,22 @@ mod tests { let mut ai = Matrix::new(5, 5); let correct_det = mat_inverse(&mut ai, &a).unwrap(); for (coo, csc, csr, det) in [ - Samples::mkl_positive_definite_5x5_lower(false), - Samples::mkl_positive_definite_5x5_lower(true), - Samples::mkl_positive_definite_5x5_upper(false), - Samples::mkl_positive_definite_5x5_upper(true), - Samples::mkl_symmetric_5x5_lower(false, false, false), - Samples::mkl_symmetric_5x5_lower(false, true, false), - Samples::mkl_symmetric_5x5_lower(false, false, true), - Samples::mkl_symmetric_5x5_lower(false, true, true), - Samples::mkl_symmetric_5x5_lower(true, false, false), - Samples::mkl_symmetric_5x5_lower(true, true, false), - Samples::mkl_symmetric_5x5_lower(true, false, true), - Samples::mkl_symmetric_5x5_lower(true, true, true), - Samples::mkl_symmetric_5x5_upper(false, false, false), - Samples::mkl_symmetric_5x5_upper(false, true, false), - Samples::mkl_symmetric_5x5_upper(false, false, true), - Samples::mkl_symmetric_5x5_upper(false, true, true), - Samples::mkl_symmetric_5x5_upper(true, false, false), - Samples::mkl_symmetric_5x5_upper(true, true, false), - Samples::mkl_symmetric_5x5_upper(true, false, true), - Samples::mkl_symmetric_5x5_upper(true, true, true), - Samples::mkl_symmetric_5x5_full(false), - Samples::mkl_symmetric_5x5_full(true), + Samples::mkl_positive_definite_5x5_lower(), + Samples::mkl_positive_definite_5x5_upper(), + Samples::mkl_symmetric_5x5_lower(false, false), + Samples::mkl_symmetric_5x5_lower(false, true), + Samples::mkl_symmetric_5x5_lower(true, false), + Samples::mkl_symmetric_5x5_lower(true, true), + Samples::mkl_symmetric_5x5_upper(false, false), + Samples::mkl_symmetric_5x5_upper(false, true), + Samples::mkl_symmetric_5x5_upper(true, false), + Samples::mkl_symmetric_5x5_upper(true, true), + Samples::mkl_symmetric_5x5_full(), ] { - let mat = coo.as_dense(); approx_eq(det, correct_det, 1e-13); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); } @@ -1297,17 +1649,14 @@ mod tests { let correct = &[[10.0, 20.0]]; for (coo, csc, csr, _) in [ - Samples::rectangular_1x2(false, false, false), - Samples::rectangular_1x2(false, true, false), - Samples::rectangular_1x2(false, false, true), - Samples::rectangular_1x2(false, true, true), - Samples::rectangular_1x2(true, false, false), - Samples::rectangular_1x2(true, true, false), - Samples::rectangular_1x2(true, false, true), - Samples::rectangular_1x2(true, true, true), + Samples::rectangular_1x2(false, false), + Samples::rectangular_1x2(false, true), + Samples::rectangular_1x2(true, false), + Samples::rectangular_1x2(true, true), ] { - let mat = coo.as_dense(); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); } @@ -1315,16 +1664,18 @@ mod tests { let correct = &[[1.0, 0.0, 3.0, 0.0, 5.0, 0.0, 7.0]]; let (coo, csc, csr, _) = Samples::rectangular_1x7(); - let mat = coo.as_dense(); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); // ---------------------------------------------------------------------------- let correct = &[[0.0], [2.0], [0.0], [4.0], [0.0], [6.0], [0.0]]; let (coo, csc, csr, _) = Samples::rectangular_7x1(); - let mat = coo.as_dense(); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); // ---------------------------------------------------------------------------- @@ -1335,8 +1686,23 @@ mod tests { [15.0, -6.0, 0.0, 3.0], // ]; let (coo, csc, csr, _) = Samples::rectangular_3x4(); - let mat = coo.as_dense(); - mat_approx_eq(&mat, correct, 1e-15); + mat_approx_eq(&coo.as_dense(), correct, 1e-15); + mat_approx_eq(&csc.as_dense(), correct, 1e-15); + mat_approx_eq(&csr.as_dense(), correct, 1e-15); + check(&coo, &csc, &csr); + + // ---------------------------------------------------------------------------- + + let correct = &[ + [cpx!(4.0, 4.0), cpx!(0.0, 0.0), cpx!(2.0, 2.0)], // + [cpx!(0.0, 0.0), cpx!(1.0, 0.0), cpx!(3.0, 3.0)], // + [cpx!(0.0, 0.0), cpx!(5.0, 5.0), cpx!(1.0, 1.0)], // + [cpx!(1.0, 0.0), cpx!(0.0, 0.0), cpx!(0.0, 0.0)], // + ]; + let (coo, csc, csr, _) = Samples::complex_rectangular_4x3(); + complex_mat_approx_eq(&coo.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csc.as_dense(), correct, 1e-15); + complex_mat_approx_eq(&csr.as_dense(), correct, 1e-15); check(&coo, &csc, &csr); } } diff --git a/russell_sparse/src/solver_intel_dss.rs b/russell_sparse/src/solver_intel_dss.rs index 72e34450..02c188cd 100644 --- a/russell_sparse/src/solver_intel_dss.rs +++ b/russell_sparse/src/solver_intel_dss.rs @@ -12,6 +12,16 @@ struct InterfaceIntelDSS { _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } +/// Enforce Send on the C structure +/// +/// +unsafe impl Send for InterfaceIntelDSS {} + +/// Enforce Send on the Rust structure +/// +/// +unsafe impl Send for SolverIntelDSS {} + extern "C" { fn solver_intel_dss_new() -> *mut InterfaceIntelDSS; fn solver_intel_dss_drop(solver: *mut InterfaceIntelDSS); @@ -51,7 +61,7 @@ pub struct SolverIntelDSS { factorized: bool, /// Holds the symmetry type used in initialize - initialized_symmetry: Option, + initialized_symmetry: Symmetry, /// Holds the matrix dimension saved in initialize initialized_ndim: usize, @@ -108,12 +118,12 @@ impl SolverIntelDSS { solver, initialized: false, factorized: false, - initialized_symmetry: None, + initialized_symmetry: Symmetry::No, initialized_ndim: 0, initialized_nnz: 0, determinant_coefficient: 0.0, determinant_exponent: 0.0, - stopwatch: Stopwatch::new(""), + stopwatch: Stopwatch::new(), time_initialize_ns: 0, time_factorize_ns: 0, time_solve_ns: 0, @@ -185,10 +195,7 @@ impl LinSolTrait for SolverIntelDSS { let calc_det = if par.compute_determinant { 1 } else { 0 }; // extract the symmetry flags and check the storage type - let (general_symmetric, positive_definite) = match csr.symmetry { - Some(symmetry) => symmetry.status(false, true)?, - None => (0, 0), - }; + let (general_symmetric, positive_definite) = csr.symmetry.status(false, true)?; // matrix config let ndim = to_i32(csr.nrow); @@ -306,7 +313,8 @@ impl LinSolTrait for SolverIntelDSS { } else { "INTEL_DSS_IS_NOT_AVAILABLE".to_string() }; - stats.determinant.mantissa = self.determinant_coefficient; + stats.determinant.mantissa_real = self.determinant_coefficient; + stats.determinant.mantissa_imag = 0.0; stats.determinant.base = 10.0; stats.determinant.exponent = self.determinant_exponent; stats.time_nanoseconds.initialize = self.time_initialize_ns; @@ -377,7 +385,7 @@ mod tests { assert!(!solver.factorized); // COO to CSR errors - let coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let coo = CooMatrix::new(1, 1, 1, None).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), @@ -391,7 +399,7 @@ mod tests { solver.factorize(&mut mat, None).err(), Some("the matrix must be square") ); - let (coo, _, _, _) = Samples::mkl_symmetric_5x5_lower(false, false, false); + let (coo, _, _, _) = Samples::mkl_symmetric_5x5_lower(false, false); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), @@ -399,14 +407,14 @@ mod tests { ); // check already factorized data - let mut coo = CooMatrix::new(2, 2, 2, None, false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); // ... factorize once => OK solver.factorize(&mut mat, None).unwrap(); // ... change matrix (symmetry) - let mut coo = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full)), false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); @@ -415,7 +423,7 @@ mod tests { Some("subsequent factorizations must use the same matrix (symmetry differs)") ); // ... change matrix (ndim) - let mut coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let mut coo = CooMatrix::new(1, 1, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( @@ -423,7 +431,7 @@ mod tests { Some("subsequent factorizations must use the same matrix (ndim differs)") ); // ... change matrix (nnz) - let mut coo = CooMatrix::new(2, 2, 1, None, false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( @@ -436,7 +444,7 @@ mod tests { fn factorize_works() { let mut solver = SolverIntelDSS::new().unwrap(); assert!(!solver.factorized); - let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(false); + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); let mut mat = SparseMatrix::from_coo(coo); let mut params = LinSolParams::new(); @@ -456,7 +464,7 @@ mod tests { #[test] fn factorize_fails_on_singular_matrix() { let mut solver = SolverIntelDSS::new().unwrap(); - let mut coo = CooMatrix::new(2, 2, 2, None, false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 0.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); @@ -466,33 +474,67 @@ mod tests { #[test] fn solve_handles_errors() { - let (coo, _, _, _) = Samples::tiny_1x1(false); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, 123.0).unwrap(); + coo.put(1, 1, 456.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); let mut solver = SolverIntelDSS::new().unwrap(); assert!(!solver.factorized); let mut x = Vector::new(2); - let rhs = Vector::new(1); + let rhs = Vector::new(2); assert_eq!( solver.solve(&mut x, &mut mat, &rhs, false), Err("the function factorize must be called before solve") ); + let mut x = Vector::new(1); solver.factorize(&mut mat, None).unwrap(); assert_eq!( solver.solve(&mut x, &mut mat, &rhs, false), Err("the dimension of the vector of unknown values x is incorrect") ); - let mut x = Vector::new(1); - let rhs = Vector::new(2); + let mut x = Vector::new(2); + let rhs = Vector::new(1); assert_eq!( solver.solve(&mut x, &mut mat, &rhs, false), Err("the dimension of the right-hand side vector is incorrect") ); + // wrong symmetry + let rhs = Vector::new(2); + let mut coo_wrong = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + coo_wrong.put(1, 1, 456.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csr_or_from_coo().unwrap(); // make sure to convert to CSR (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (symmetry differs)") + ); + // wrong ndim + let mut coo_wrong = CooMatrix::new(1, 1, 1, None).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csr_or_from_coo().unwrap(); // make sure to convert to CSR (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (ndim differs)") + ); + // wrong nnz + let mut coo_wrong = CooMatrix::new(2, 2, 3, None).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + coo_wrong.put(1, 1, 123.0).unwrap(); + coo_wrong.put(0, 1, 100.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csr_or_from_coo().unwrap(); // make sure to convert to CSR (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (nnz differs)") + ); } #[test] fn solve_works() { let mut solver = SolverIntelDSS::new().unwrap(); - let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(false); + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); let mut mat = SparseMatrix::from_coo(coo); let mut x = Vector::new(5); let rhs = Vector::from(&[8.0, 45.0, -3.0, 3.0, 19.0]); diff --git a/russell_sparse/src/solver_mumps.rs b/russell_sparse/src/solver_mumps.rs index 62f6fe6a..b0fcdca0 100644 --- a/russell_sparse/src/solver_mumps.rs +++ b/russell_sparse/src/solver_mumps.rs @@ -13,6 +13,16 @@ struct InterfaceMUMPS { _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } +/// Enforce Send on the C structure +/// +/// +unsafe impl Send for InterfaceMUMPS {} + +/// Enforce Send on the Rust structure +/// +/// +unsafe impl Send for SolverMUMPS {} + extern "C" { fn solver_mumps_new() -> *mut InterfaceMUMPS; fn solver_mumps_drop(solver: *mut InterfaceMUMPS); @@ -114,7 +124,7 @@ pub struct SolverMUMPS { factorized: bool, /// Holds the symmetry type used in the initialize - initialized_symmetry: Option, + initialized_symmetry: Symmetry, /// Holds the matrix dimension saved in initialize initialized_ndim: usize, @@ -160,6 +170,12 @@ pub struct SolverMUMPS { /// Time spent on solve in nanoseconds time_solve_ns: u128, + + /// Holds the (one-based/Fortran) row indices i + fortran_indices_i: Vec, + + /// Holds the (one-based/Fortran) column indices j + fortran_indices_j: Vec, } impl Drop for SolverMUMPS { @@ -183,7 +199,7 @@ impl SolverMUMPS { solver, initialized: false, factorized: false, - initialized_symmetry: None, + initialized_symmetry: Symmetry::No, initialized_ndim: 0, initialized_nnz: 0, effective_ordering: -1, @@ -193,10 +209,12 @@ impl SolverMUMPS { determinant_exponent: 0.0, error_analysis_option: 0, error_analysis_array_len_8: vec![0.0; 8], - stopwatch: Stopwatch::new(""), + stopwatch: Stopwatch::new(), time_initialize_ns: 0, time_factorize_ns: 0, time_solve_ns: 0, + fortran_indices_i: Vec::new(), + fortran_indices_j: Vec::new(), }) } } @@ -228,9 +246,6 @@ impl LinSolTrait for SolverMUMPS { let coo = mat.get_coo()?; // check the COO matrix - if !coo.one_based { - return Err("the COO matrix must have one-based (FORTRAN) indices as required by MUMPS"); - } if coo.nrow != coo.ncol { return Err("the COO matrix must be square"); } @@ -253,6 +268,12 @@ impl LinSolTrait for SolverMUMPS { self.initialized_symmetry = coo.symmetry; self.initialized_ndim = coo.nrow; self.initialized_nnz = coo.nnz; + self.fortran_indices_i = vec![0; coo.nnz]; + self.fortran_indices_j = vec![0; coo.nnz]; + for k in 0..coo.nnz { + self.fortran_indices_i[k] = coo.indices_i[k] + 1; + self.fortran_indices_j[k] = coo.indices_j[k] + 1; + } } // configuration parameters @@ -284,10 +305,7 @@ impl LinSolTrait for SolverMUMPS { let verbose = if par.verbose { 1 } else { 0 }; // extract the symmetry flags and check the storage type - let (general_symmetric, positive_definite) = match coo.symmetry { - Some(symmetry) => symmetry.status(true, false)?, - None => (0, 0), - }; + let (general_symmetric, positive_definite) = coo.symmetry.status(true, false)?; // matrix config let ndim = to_i32(coo.nrow); @@ -309,8 +327,8 @@ impl LinSolTrait for SolverMUMPS { positive_definite, ndim, nnz, - coo.indices_i.as_ptr(), - coo.indices_j.as_ptr(), + self.fortran_indices_i.as_ptr(), + self.fortran_indices_j.as_ptr(), coo.values.as_ptr(), ); if status != SUCCESSFUL_EXIT { @@ -422,7 +440,8 @@ impl LinSolTrait for SolverMUMPS { } else { "MUMPS".to_string() }; - stats.determinant.mantissa = self.determinant_coefficient; + stats.determinant.mantissa_real = self.determinant_coefficient; + stats.determinant.mantissa_imag = 0.0; stats.determinant.base = 2.0; stats.determinant.exponent = self.determinant_exponent; stats.output.effective_ordering = match self.effective_ordering { @@ -461,23 +480,23 @@ impl LinSolTrait for SolverMUMPS { } } -const MUMPS_ORDERING_AMD: i32 = 0; // Amd (page 35) -const MUMPS_ORDERING_AMF: i32 = 2; // Amf (page 35) -const MUMPS_ORDERING_AUTO: i32 = 7; // Auto (page 36) -const MUMPS_ORDERING_METIS: i32 = 5; // Metis (page 35) -const MUMPS_ORDERING_PORD: i32 = 4; // Pord (page 35) -const MUMPS_ORDERING_QAMD: i32 = 6; // Qamd (page 35) -const MUMPS_ORDERING_SCOTCH: i32 = 3; // Scotch (page 35) - -const MUMPS_SCALING_AUTO: i32 = 77; // Auto (page 33) -const MUMPS_SCALING_COLUMN: i32 = 3; // Column (page 33) -const MUMPS_SCALING_DIAGONAL: i32 = 1; // Diagonal (page 33) -const MUMPS_SCALING_NO: i32 = 0; // No (page 33) -const MUMPS_SCALING_ROW_COL: i32 = 4; // RowCol (page 33) -const MUMPS_SCALING_ROW_COL_ITER: i32 = 7; // RowColIter (page 33) -const MUMPS_SCALING_ROW_COL_RIG: i32 = 8; // RowColRig (page 33) - -fn mumps_ordering(ordering: Ordering) -> i32 { +pub(crate) const MUMPS_ORDERING_AMD: i32 = 0; // Amd (page 35) +pub(crate) const MUMPS_ORDERING_AMF: i32 = 2; // Amf (page 35) +pub(crate) const MUMPS_ORDERING_AUTO: i32 = 7; // Auto (page 36) +pub(crate) const MUMPS_ORDERING_METIS: i32 = 5; // Metis (page 35) +pub(crate) const MUMPS_ORDERING_PORD: i32 = 4; // Pord (page 35) +pub(crate) const MUMPS_ORDERING_QAMD: i32 = 6; // Qamd (page 35) +pub(crate) const MUMPS_ORDERING_SCOTCH: i32 = 3; // Scotch (page 35) + +pub(crate) const MUMPS_SCALING_AUTO: i32 = 77; // Auto (page 33) +pub(crate) const MUMPS_SCALING_COLUMN: i32 = 3; // Column (page 33) +pub(crate) const MUMPS_SCALING_DIAGONAL: i32 = 1; // Diagonal (page 33) +pub(crate) const MUMPS_SCALING_NO: i32 = 0; // No (page 33) +pub(crate) const MUMPS_SCALING_ROW_COL: i32 = 4; // RowCol (page 33) +pub(crate) const MUMPS_SCALING_ROW_COL_ITER: i32 = 7; // RowColIter (page 33) +pub(crate) const MUMPS_SCALING_ROW_COL_RIG: i32 = 8; // RowColRig (page 33) + +pub(crate) fn mumps_ordering(ordering: Ordering) -> i32 { match ordering { Ordering::Amd => MUMPS_ORDERING_AMD, // Amd (page 35) Ordering::Amf => MUMPS_ORDERING_AMF, // Amf (page 35) @@ -492,7 +511,7 @@ fn mumps_ordering(ordering: Ordering) -> i32 { } } -fn mumps_scaling(scaling: Scaling) -> i32 { +pub(crate) fn mumps_scaling(scaling: Scaling) -> i32 { match scaling { Scaling::Auto => MUMPS_SCALING_AUTO, // Auto (page 33) Scaling::Column => MUMPS_SCALING_COLUMN, // Column (page 33) @@ -507,7 +526,7 @@ fn mumps_scaling(scaling: Scaling) -> i32 { } /// Handles error code -fn handle_mumps_error_code(err: i32) -> StrError { +pub(crate) fn handle_mumps_error_code(err: i32) -> StrError { match err { -1 => "Error(-1): error on some processor", -2 => "Error(-2): nnz is out of range", @@ -599,24 +618,20 @@ mod tests { use super::*; use crate::{CooMatrix, LinSolParams, LinSolTrait, Ordering, Samples, Scaling, SparseMatrix, Storage, Symmetry}; use russell_lab::{approx_eq, vec_approx_eq, Vector}; + use serial_test::serial; - #[test] - fn complete_solution_cycle_works() { - // IMPORTANT: - // Since MUMPS is not thread-safe, we need to call all MUMPS functions - // in a single test unit because the tests are run in parallel by default - - // allocate x and rhs - let mut x = Vector::new(5); - let rhs = Vector::from(&[8.0, 45.0, -3.0, 3.0, 19.0]); - let x_correct = &[1.0, 2.0, 3.0, 4.0, 5.0]; + // IMPORTANT: + // Since MUMPS is not thread-safe, we need to use serial_test::serial + #[test] + #[serial] + fn factorize_handles_errors() { // allocate a new solver let mut solver = SolverMUMPS::new().unwrap(); assert!(!solver.factorized); // get COO matrix errors - let (_, csc, _, _) = Samples::tiny_1x1(true); + let (_, csc, _, _) = Samples::tiny_1x1(); let mut mat = SparseMatrix::from_csc(csc); assert_eq!( solver.factorize(&mut mat, None).err(), @@ -624,25 +639,19 @@ mod tests { ); // check COO matrix - let coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); - let mut mat = SparseMatrix::from_coo(coo); - assert_eq!( - solver.factorize(&mut mat, None).err(), - Some("the COO matrix must have one-based (FORTRAN) indices as required by MUMPS") - ); - let (coo, _, _, _) = Samples::rectangular_1x2(true, false, false); + let (coo, _, _, _) = Samples::rectangular_1x2(true, false); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), Some("the COO matrix must be square") ); - let coo = CooMatrix::new(1, 1, 1, None, true).unwrap(); + let coo = CooMatrix::new(1, 1, 1, None).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), Some("the COO matrix must have at least one non-zero value") ); - let (coo, _, _, _) = Samples::mkl_symmetric_5x5_upper(true, false, false); + let (coo, _, _, _) = Samples::mkl_symmetric_5x5_upper(true, false); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), @@ -650,14 +659,14 @@ mod tests { ); // check already factorized data - let mut coo = CooMatrix::new(2, 2, 2, None, true).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); // ... factorize once => OK solver.factorize(&mut mat, None).unwrap(); // ... change matrix (symmetry) - let mut coo = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full)), true).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); @@ -666,7 +675,7 @@ mod tests { Some("subsequent factorizations must use the same matrix (symmetry differs)") ); // ... change matrix (ndim) - let mut coo = CooMatrix::new(1, 1, 1, None, true).unwrap(); + let mut coo = CooMatrix::new(1, 1, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( @@ -674,20 +683,102 @@ mod tests { Some("subsequent factorizations must use the same matrix (ndim differs)") ); // ... change matrix (nnz) - let mut coo = CooMatrix::new(2, 2, 1, None, true).unwrap(); + let mut coo = CooMatrix::new(2, 2, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), Some("subsequent factorizations must use the same matrix (nnz differs)") ); + } + + #[test] + #[serial] + fn factorize_fails_on_singular_matrix() { + let mut mat_singular = SparseMatrix::new_coo(5, 5, 2, None).unwrap(); + mat_singular.put(0, 0, 1.0).unwrap(); + mat_singular.put(4, 4, 1.0).unwrap(); + let mut solver = SolverMUMPS::new().unwrap(); + assert_eq!( + solver.factorize(&mut mat_singular, None), + Err("Error(-10): numerically singular matrix") + ); + } + + #[test] + #[serial] + fn solve_handles_errors() { + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, 123.0).unwrap(); + coo.put(1, 1, 456.0).unwrap(); + let mut mat = SparseMatrix::from_coo(coo); + let mut solver = SolverMUMPS::new().unwrap(); + assert!(!solver.factorized); + let mut x = Vector::new(2); + let rhs = Vector::new(2); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the function factorize must be called before solve") + ); + let mut x = Vector::new(1); + solver.factorize(&mut mat, None).unwrap(); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the dimension of the vector of unknown values x is incorrect") + ); + let mut x = Vector::new(2); + let rhs = Vector::new(1); + assert_eq!( + solver.solve(&mut x, &mut mat, &rhs, false), + Err("the dimension of the right-hand side vector is incorrect") + ); + // wrong symmetry + let rhs = Vector::new(2); + let mut coo_wrong = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + coo_wrong.put(1, 1, 456.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (symmetry differs)") + ); + // wrong ndim + let mut coo_wrong = CooMatrix::new(1, 1, 1, None).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (ndim differs)") + ); + // wrong nnz + let mut coo_wrong = CooMatrix::new(2, 2, 3, None).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + coo_wrong.put(1, 1, 123.0).unwrap(); + coo_wrong.put(0, 1, 100.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (nnz differs)") + ); + } + + #[test] + #[serial] + fn factorize_and_solve_work() { + // allocate x and rhs + let mut x = Vector::new(5); + let rhs = Vector::from(&[8.0, 45.0, -3.0, 3.0, 19.0]); + let x_correct = &[1.0, 2.0, 3.0, 4.0, 5.0]; // allocate a new solver let mut solver = SolverMUMPS::new().unwrap(); assert!(!solver.factorized); // sample matrix - let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(true); + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); let mut mat = SparseMatrix::from_coo(coo); // set params @@ -696,28 +787,10 @@ mod tests { params.scaling = Scaling::RowCol; params.compute_determinant = true; - // solve fails on non-factorized system - assert_eq!( - solver.solve(&mut x, &mat, &rhs, false), - Err("the function factorize must be called before solve") - ); - // factorize works solver.factorize(&mut mat, Some(params)).unwrap(); assert!(solver.factorized); - // solve fails on wrong x and rhs vectors - let mut x_wrong = Vector::new(3); - let rhs_wrong = Vector::new(2); - assert_eq!( - solver.solve(&mut x_wrong, &mat, &rhs, false), - Err("the dimension of the vector of unknown values x is incorrect") - ); - assert_eq!( - solver.solve(&mut x, &mat, &rhs_wrong, false), - Err("the dimension of the right-hand side vector is incorrect") - ); - // solve works solver.solve(&mut x, &mat, &rhs, false).unwrap(); vec_approx_eq(x.as_data(), x_correct, 1e-14); @@ -727,8 +800,8 @@ mod tests { assert_eq!(solver.effective_scaling, 0); // No, because we requested the determinant // check the determinant - let d = solver.determinant_coefficient * f64::powf(2.0, solver.determinant_exponent); - approx_eq(d, 114.0, 1e-13); + let det = solver.determinant_coefficient * f64::powf(2.0, solver.determinant_exponent); + approx_eq(det, 114.0, 1e-13); // update stats let mut stats = StatsLinSol::new(); @@ -741,18 +814,8 @@ mod tests { solver.solve(&mut x_again, &mat, &rhs, false).unwrap(); vec_approx_eq(x_again.as_data(), x_correct, 1e-14); - // factorize fails on singular matrix - let mut mat_singular = SparseMatrix::new_coo(5, 5, 2, None, true).unwrap(); - mat_singular.put(0, 0, 1.0).unwrap(); - mat_singular.put(4, 4, 1.0).unwrap(); - let mut solver = SolverMUMPS::new().unwrap(); - assert_eq!( - solver.factorize(&mut mat_singular, None), - Err("Error(-10): numerically singular matrix") - ); - // solve with positive-definite matrix works - let (coo_pd_lower, _, _, _) = Samples::mkl_positive_definite_5x5_lower(true); + let (coo_pd_lower, _, _, _) = Samples::mkl_positive_definite_5x5_lower(); let mut mat_pd_lower = SparseMatrix::from_coo(coo_pd_lower); params.ordering = Ordering::Auto; params.scaling = Scaling::Auto; @@ -764,12 +827,6 @@ mod tests { solver.solve(&mut x, &mat_pd_lower, &rhs, false).unwrap(); let x_correct = &[-979.0 / 3.0, 983.0, 1961.0 / 12.0, 398.0, 123.0 / 2.0]; vec_approx_eq(x.as_data(), x_correct, 1e-10); - - // solve with different matrix fails - assert_eq!( - solver.solve(&mut x, &mat, &rhs, false).err(), - Some("solve must use the same matrix (symmetry differs)") - ); } #[test] diff --git a/russell_sparse/src/solver_umfpack.rs b/russell_sparse/src/solver_umfpack.rs index 47a533bc..eab1dd46 100644 --- a/russell_sparse/src/solver_umfpack.rs +++ b/russell_sparse/src/solver_umfpack.rs @@ -12,6 +12,16 @@ struct InterfaceUMFPACK { _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, } +/// Enforce Send on the C structure +/// +/// +unsafe impl Send for InterfaceUMFPACK {} + +/// Enforce Send on the Rust structure +/// +/// +unsafe impl Send for SolverUMFPACK {} + extern "C" { fn solver_umfpack_new() -> *mut InterfaceUMFPACK; fn solver_umfpack_drop(solver: *mut InterfaceUMFPACK); @@ -65,7 +75,7 @@ pub struct SolverUMFPACK { factorized: bool, /// Holds the symmetry type used in initialize - initialized_symmetry: Option, + initialized_symmetry: Symmetry, /// Holds the matrix dimension saved in initialize initialized_ndim: usize, @@ -129,7 +139,7 @@ impl SolverUMFPACK { solver, initialized: false, factorized: false, - initialized_symmetry: None, + initialized_symmetry: Symmetry::No, initialized_ndim: 0, initialized_nnz: 0, effective_strategy: -1, @@ -138,7 +148,7 @@ impl SolverUMFPACK { rcond_estimate: 0.0, determinant_coefficient: 0.0, determinant_exponent: 0.0, - stopwatch: Stopwatch::new(""), + stopwatch: Stopwatch::new(), time_initialize_ns: 0, time_factorize_ns: 0, time_solve_ns: 0, @@ -176,10 +186,8 @@ impl LinSolTrait for SolverUMFPACK { if csc.nrow != csc.ncol { return Err("the matrix must be square"); } - if let Some(symmetry) = csc.symmetry { - if symmetry.triangular() { - return Err("for UMFPACK, the matrix must not be triangular"); - } + if csc.symmetry.triangular() { + return Err("for UMFPACK, the matrix must not be triangular"); } // check already initialized data @@ -345,7 +353,8 @@ impl LinSolTrait for SolverUMFPACK { } else { "UMFPACK".to_string() }; - stats.determinant.mantissa = self.determinant_coefficient; + stats.determinant.mantissa_real = self.determinant_coefficient; + stats.determinant.mantissa_imag = 0.0; stats.determinant.base = 10.0; stats.determinant.exponent = self.determinant_exponent; stats.output.umfpack_rcond_estimate = self.rcond_estimate; @@ -375,23 +384,24 @@ impl LinSolTrait for SolverUMFPACK { } } -const UMFPACK_STRATEGY_AUTO: i32 = 0; // use symmetric or unsymmetric strategy -const UMFPACK_STRATEGY_UNSYMMETRIC: i32 = 1; // COLAMD(A), col-tree post-order, do not prefer diag -const UMFPACK_STRATEGY_SYMMETRIC: i32 = 3; // AMD(A+A'), no col-tree post-order, prefer diagonal +pub(crate) const UMFPACK_STRATEGY_AUTO: i32 = 0; // use symmetric or unsymmetric strategy +pub(crate) const UMFPACK_STRATEGY_UNSYMMETRIC: i32 = 1; // COLAMD(A), col-tree post-order, do not prefer diag +pub(crate) const UMFPACK_STRATEGY_SYMMETRIC: i32 = 3; // AMD(A+A'), no col-tree post-order, prefer diagonal -const UMFPACK_ORDERING_CHOLMOD: i32 = 0; // use CHOLMOD (AMD/COLAMD then METIS) -const UMFPACK_ORDERING_AMD: i32 = 1; // use AMD/COLAMD -const UMFPACK_ORDERING_METIS: i32 = 3; // use METIS -const UMFPACK_ORDERING_BEST: i32 = 4; // try many orderings, pick best -const UMFPACK_ORDERING_NONE: i32 = 5; // natural ordering -const UMFPACK_DEFAULT_ORDERING: i32 = UMFPACK_ORDERING_AMD; +pub(crate) const UMFPACK_ORDERING_CHOLMOD: i32 = 0; // use CHOLMOD (AMD/COLAMD then METIS) +pub(crate) const UMFPACK_ORDERING_AMD: i32 = 1; // use AMD/COLAMD +pub(crate) const UMFPACK_ORDERING_METIS: i32 = 3; // use METIS +pub(crate) const UMFPACK_ORDERING_BEST: i32 = 4; // try many orderings, pick best +pub(crate) const UMFPACK_ORDERING_NONE: i32 = 5; // natural ordering +pub(crate) const UMFPACK_DEFAULT_ORDERING: i32 = UMFPACK_ORDERING_AMD; -const UMFPACK_SCALE_NONE: i32 = 0; // no scaling -const UMFPACK_SCALE_SUM: i32 = 1; // default: divide each row by sum (abs (row)) -const UMFPACK_SCALE_MAX: i32 = 2; // divide each row by max (abs (row)) -const UMFPACK_DEFAULT_SCALE: i32 = UMFPACK_SCALE_SUM; +pub(crate) const UMFPACK_SCALE_NONE: i32 = 0; // no scaling +pub(crate) const UMFPACK_SCALE_SUM: i32 = 1; // default: divide each row by sum (abs (row)) +pub(crate) const UMFPACK_SCALE_MAX: i32 = 2; // divide each row by max (abs (row)) +pub(crate) const UMFPACK_DEFAULT_SCALE: i32 = UMFPACK_SCALE_SUM; -fn umfpack_ordering(ordering: Ordering) -> i32 { +/// Returns the UMFPACK ordering constant +pub(crate) fn umfpack_ordering(ordering: Ordering) -> i32 { match ordering { Ordering::Amd => UMFPACK_ORDERING_AMD, Ordering::Amf => UMFPACK_DEFAULT_ORDERING, @@ -406,7 +416,8 @@ fn umfpack_ordering(ordering: Ordering) -> i32 { } } -fn umfpack_scaling(scaling: Scaling) -> i32 { +/// Returns the UMFPACK scaling constant +pub(crate) fn umfpack_scaling(scaling: Scaling) -> i32 { match scaling { Scaling::Auto => UMFPACK_DEFAULT_SCALE, Scaling::Column => UMFPACK_DEFAULT_SCALE, @@ -470,7 +481,7 @@ mod tests { assert!(!solver.factorized); // COO to CSC errors - let coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let coo = CooMatrix::new(1, 1, 1, None).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), @@ -484,7 +495,7 @@ mod tests { solver.factorize(&mut mat, None).err(), Some("the matrix must be square") ); - let (coo, _, _, _) = Samples::mkl_symmetric_5x5_lower(false, false, false); + let (coo, _, _, _) = Samples::mkl_symmetric_5x5_lower(false, false); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( solver.factorize(&mut mat, None).err(), @@ -492,14 +503,14 @@ mod tests { ); // check already factorized data - let mut coo = CooMatrix::new(2, 2, 2, None, false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); // ... factorize once => OK solver.factorize(&mut mat, None).unwrap(); // ... change matrix (symmetry) - let mut coo = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full)), false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 2.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); @@ -508,7 +519,7 @@ mod tests { Some("subsequent factorizations must use the same matrix (symmetry differs)") ); // ... change matrix (ndim) - let mut coo = CooMatrix::new(1, 1, 1, None, false).unwrap(); + let mut coo = CooMatrix::new(1, 1, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( @@ -516,7 +527,7 @@ mod tests { Some("subsequent factorizations must use the same matrix (ndim differs)") ); // ... change matrix (nnz) - let mut coo = CooMatrix::new(2, 2, 1, None, false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); assert_eq!( @@ -529,7 +540,7 @@ mod tests { fn factorize_works() { let mut solver = SolverUMFPACK::new().unwrap(); assert!(!solver.factorized); - let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(false); + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); let mut mat = SparseMatrix::from_coo(coo); let mut params = LinSolParams::new(); @@ -555,7 +566,7 @@ mod tests { #[test] fn factorize_fails_on_singular_matrix() { let mut solver = SolverUMFPACK::new().unwrap(); - let mut coo = CooMatrix::new(2, 2, 2, None, false).unwrap(); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(1, 1, 0.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); @@ -564,33 +575,67 @@ mod tests { #[test] fn solve_handles_errors() { - let (coo, _, _, _) = Samples::tiny_1x1(false); + let mut coo = CooMatrix::new(2, 2, 2, None).unwrap(); + coo.put(0, 0, 123.0).unwrap(); + coo.put(1, 1, 456.0).unwrap(); let mut mat = SparseMatrix::from_coo(coo); let mut solver = SolverUMFPACK::new().unwrap(); assert!(!solver.factorized); let mut x = Vector::new(2); - let rhs = Vector::new(1); + let rhs = Vector::new(2); assert_eq!( solver.solve(&mut x, &mut mat, &rhs, false), Err("the function factorize must be called before solve") ); + let mut x = Vector::new(1); solver.factorize(&mut mat, None).unwrap(); assert_eq!( solver.solve(&mut x, &mut mat, &rhs, false), Err("the dimension of the vector of unknown values x is incorrect") ); - let mut x = Vector::new(1); - let rhs = Vector::new(2); + let mut x = Vector::new(2); + let rhs = Vector::new(1); assert_eq!( solver.solve(&mut x, &mut mat, &rhs, false), Err("the dimension of the right-hand side vector is incorrect") ); + // wrong symmetry + let rhs = Vector::new(2); + let mut coo_wrong = CooMatrix::new(2, 2, 2, Some(Symmetry::General(Storage::Full))).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + coo_wrong.put(1, 1, 456.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (symmetry differs)") + ); + // wrong ndim + let mut coo_wrong = CooMatrix::new(1, 1, 1, None).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (ndim differs)") + ); + // wrong nnz + let mut coo_wrong = CooMatrix::new(2, 2, 3, None).unwrap(); + coo_wrong.put(0, 0, 123.0).unwrap(); + coo_wrong.put(1, 1, 123.0).unwrap(); + coo_wrong.put(0, 1, 100.0).unwrap(); + let mut mat_wrong = SparseMatrix::from_coo(coo_wrong); + mat_wrong.get_csc_or_from_coo().unwrap(); // make sure to convert to CSC (because we're not calling factorize on this wrong matrix) + assert_eq!( + solver.solve(&mut x, &mut mat_wrong, &rhs, false), + Err("solve must use the same matrix (nnz differs)") + ); } #[test] fn solve_works() { let mut solver = SolverUMFPACK::new().unwrap(); - let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(false); + let (coo, _, _, _) = Samples::umfpack_unsymmetric_5x5(); let mut mat = SparseMatrix::from_coo(coo); let mut x = Vector::new(5); let rhs = Vector::from(&[8.0, 45.0, -3.0, 3.0, 19.0]); diff --git a/russell_sparse/src/sparse_matrix.rs b/russell_sparse/src/sparse_matrix.rs index 4381ee41..c84ff200 100644 --- a/russell_sparse/src/sparse_matrix.rs +++ b/russell_sparse/src/sparse_matrix.rs @@ -1,39 +1,56 @@ -use super::{CooMatrix, CscMatrix, CsrMatrix, Symmetry}; +use super::{NumCooMatrix, NumCscMatrix, NumCsrMatrix, Symmetry}; use crate::StrError; -use russell_lab::{find_index_abs_max, Matrix, Vector}; -use std::ffi::OsStr; +use num_traits::{Num, NumCast}; +use russell_lab::{NumMatrix, NumVector}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::ops::{AddAssign, MulAssign}; /// Unifies the sparse matrix representations by wrapping COO, CSC, and CSR structures /// -/// This structure is simply: +/// This structure is a wrapper around COO, CSC, or CSR matrices. For instance: /// /// ```text -/// pub struct SparseMatrix { -/// coo: Option, -/// csc: Option, -/// csr: Option, +/// pub struct NumSparseMatrix { +/// coo: Option>, +/// csc: Option>, +/// csr: Option>, /// } /// ``` /// /// # Notes /// -/// 1. At least one of [CooMatrix], [CscMatrix], or [CsrMatrix] will be `Some` +/// 1. At least one of [NumCooMatrix], [NumCscMatrix], or [NumCsrMatrix] will be `Some` /// 2. `(COO and CSC)` or `(COO and CSR)` pairs may be `Some` at the same time -/// 3. When getting data/information from the SparseMatrix, the default priority is `CSC -> CSR -> COO` +/// 3. When getting data/information from the sparse matrix, the default priority is `CSC -> CSR -> COO` /// 4. If needed, the CSC or CSR are automatically computed from COO -#[derive(Clone)] -pub struct SparseMatrix { - coo: Option, - csc: Option, - csr: Option, +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct NumSparseMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ + // Holds the COO version + #[serde(bound(deserialize = "NumCooMatrix: Deserialize<'de>"))] + coo: Option>, + + // Holds the CSC version (will not co-exist with CSR) + #[serde(bound(deserialize = "NumCscMatrix: Deserialize<'de>"))] + csc: Option>, + + // Holds the CSR version (will not co-exist with CSC) + #[serde(bound(deserialize = "NumCsrMatrix: Deserialize<'de>"))] + csr: Option>, } -impl SparseMatrix { - /// Allocates a new SparseMatrix as COO to be later updated with put and reset methods +impl NumSparseMatrix +where + T: AddAssign + MulAssign + Num + NumCast + Copy + DeserializeOwned + Serialize, +{ + /// Allocates a new sparse matrix as COO to be later updated with put and reset methods /// /// **Note:** This is the most convenient structure for recurrent updates of the sparse - /// matrix data; e.g. in finite element simulation codes. See the [CooMatrix::put] and - /// [CooMatrix::reset] functions for more details. + /// matrix data; e.g. in finite element simulation codes. See the [NumCooMatrix::put] and + /// [NumCooMatrix::reset] functions for more details. /// /// # Input /// @@ -42,22 +59,15 @@ impl SparseMatrix { /// * `max_nnz` -- (≥ 1) Maximum number of entries ≥ nnz (number of non-zeros), /// including entries with repeated indices. (must be fit i32) /// * `symmetry` -- Defines the symmetry/storage, if any - /// * `one_based` -- Use one-based indices; e.g., for MUMPS or other FORTRAN routines - pub fn new_coo( - nrow: usize, - ncol: usize, - max_nnz: usize, - symmetry: Option, - one_based: bool, - ) -> Result { - Ok(SparseMatrix { - coo: Some(CooMatrix::new(nrow, ncol, max_nnz, symmetry, one_based)?), + pub fn new_coo(nrow: usize, ncol: usize, max_nnz: usize, symmetry: Option) -> Result { + Ok(NumSparseMatrix { + coo: Some(NumCooMatrix::new(nrow, ncol, max_nnz, symmetry)?), csc: None, csr: None, }) } - /// Allocates a new SparseMatrix as CSC from the underlying arrays + /// Allocates a new sparse matrix as CSC from the underlying arrays /// /// **Note:** The column pointers and row indices must be **sorted** in ascending order. /// @@ -87,17 +97,24 @@ impl SparseMatrix { ncol: usize, col_pointers: Vec, row_indices: Vec, - values: Vec, + values: Vec, symmetry: Option, ) -> Result { - Ok(SparseMatrix { + Ok(NumSparseMatrix { coo: None, - csc: Some(CscMatrix::new(nrow, ncol, col_pointers, row_indices, values, symmetry)?), + csc: Some(NumCscMatrix::new( + nrow, + ncol, + col_pointers, + row_indices, + values, + symmetry, + )?), csr: None, }) } - /// Allocates a new SparseMatrix as CSR from the underlying arrays + /// Allocates a new sparse matrix as CSR from the underlying arrays /// /// **Note:** The row pointers and column indices must be **sorted** in ascending order. /// @@ -127,37 +144,44 @@ impl SparseMatrix { ncol: usize, row_pointers: Vec, col_indices: Vec, - values: Vec, + values: Vec, symmetry: Option, ) -> Result { - Ok(SparseMatrix { + Ok(NumSparseMatrix { coo: None, csc: None, - csr: Some(CsrMatrix::new(nrow, ncol, row_pointers, col_indices, values, symmetry)?), + csr: Some(NumCsrMatrix::new( + nrow, + ncol, + row_pointers, + col_indices, + values, + symmetry, + )?), }) } - /// Creates a new SparseMatrix from COO (move occurs) - pub fn from_coo(coo: CooMatrix) -> Self { - SparseMatrix { + /// Creates a new sparse matrix from COO (move occurs) + pub fn from_coo(coo: NumCooMatrix) -> Self { + NumSparseMatrix { coo: Some(coo), csc: None, csr: None, } } - /// Creates a new SparseMatrix from CSC (move occurs) - pub fn from_csc(csc: CscMatrix) -> Self { - SparseMatrix { + /// Creates a new sparse matrix from CSC (move occurs) + pub fn from_csc(csc: NumCscMatrix) -> Self { + NumSparseMatrix { coo: None, csc: Some(csc), csr: None, } } - /// Creates a new SparseMatrix from CSR (move occurs) - pub fn from_csr(csr: CsrMatrix) -> Self { - SparseMatrix { + /// Creates a new sparse matrix from CSR (move occurs) + pub fn from_csr(csr: NumCsrMatrix) -> Self { + NumSparseMatrix { coo: None, csc: None, csr: Some(csr), @@ -169,7 +193,7 @@ impl SparseMatrix { /// Returns `(nrow, ncol, nnz, symmetry)` /// /// **Priority**: CSC -> CSR -> COO - pub fn get_info(&self) -> (usize, usize, usize, Option) { + pub fn get_info(&self) -> (usize, usize, usize, Symmetry) { match &self.csc { Some(csc) => csc.get_info(), None => match &self.csr { @@ -179,19 +203,17 @@ impl SparseMatrix { } } - /// Returns the maximum absolute value among all values + /// Get an access to the values /// /// **Priority**: CSC -> CSR -> COO - pub fn get_max_abs_value(&self) -> f64 { - let values = match &self.csc { - Some(csc) => &csc.values, + pub fn get_values(&self) -> &[T] { + match &self.csc { + Some(csc) => csc.get_values(), None => match &self.csr { - Some(csr) => &csr.values, - None => &self.coo.as_ref().unwrap().values, // unwrap OK because at least one mat must be available + Some(csr) => csr.get_values(), + None => self.coo.as_ref().unwrap().get_values(), // unwrap OK because at least one mat must be available }, - }; - let idx = find_index_abs_max(values); - f64::abs(values[idx as usize]) + } } /// Performs the matrix-vector multiplication @@ -210,7 +232,7 @@ impl SparseMatrix { /// * `v` -- Vector with dimension equal to the number of rows of the matrix /// /// **Priority**: CSC -> CSR -> COO - pub fn mat_vec_mul(&self, v: &mut Vector, alpha: f64, u: &Vector) -> Result<(), StrError> { + pub fn mat_vec_mul(&self, v: &mut NumVector, alpha: T, u: &NumVector) -> Result<(), StrError> { match &self.csc { Some(csc) => csc.mat_vec_mul(v, alpha, u), None => match &self.csr { @@ -223,7 +245,7 @@ impl SparseMatrix { /// Converts the sparse matrix to dense format /// /// **Priority**: CSC -> CSR -> COO - pub fn as_dense(&self) -> Matrix { + pub fn as_dense(&self) -> NumMatrix { match &self.csc { Some(csc) => csc.as_dense(), None => match &self.csr { @@ -236,7 +258,7 @@ impl SparseMatrix { /// Converts the sparse matrix to dense format /// /// **Priority**: CSC -> CSR -> COO - pub fn to_dense(&self, a: &mut Matrix) -> Result<(), StrError> { + pub fn to_dense(&self, a: &mut NumMatrix) -> Result<(), StrError> { match &self.csc { Some(csc) => csc.to_dense(a), None => match &self.csr { @@ -246,38 +268,6 @@ impl SparseMatrix { } } - /// Writes a MatrixMarket file from a CooMatrix - /// - /// # Input - /// - /// * `full_path` -- may be a String, &str, or Path - /// * `vismatrix` -- generate a SMAT file for Vismatrix instead of a MatrixMarket - /// - /// **Note:** The vismatrix format is is similar to the MatrixMarket format - /// without the header, and the indices start at zero. - /// - /// # References - /// - /// * MatrixMarket: - /// * Vismatrix: - /// - /// **Priority**: CSC -> CSR -> (CSC from COO) - pub fn write_matrix_market

(&mut self, full_path: &P, vismatrix: bool) -> Result<(), StrError> - where - P: AsRef + ?Sized, - { - match &self.csc { - Some(csc) => csc.write_matrix_market(full_path, vismatrix), - None => match &self.csr { - Some(csr) => csr.write_matrix_market(full_path, vismatrix), - None => { - let csc = self.get_csc_or_from_coo()?; - csc.write_matrix_market(full_path, vismatrix) - } - }, - } - } - // COO ------------------------------------------------------------------------ /// Puts a new entry and updates pos (may be duplicate) @@ -287,7 +277,7 @@ impl SparseMatrix { /// * `i` -- row index (indices start at zero; zero-based) /// * `j` -- column index (indices start at zero; zero-based) /// * `aij` -- the value A(i,j) - pub fn put(&mut self, i: usize, j: usize, aij: f64) -> Result<(), StrError> { + pub fn put(&mut self, i: usize, j: usize, aij: T) -> Result<(), StrError> { match &mut self.coo { Some(coo) => coo.put(i, j, aij), None => Err("COO matrix is not available to put items"), @@ -308,7 +298,7 @@ impl SparseMatrix { } /// Returns a read-only access to the COO matrix, if available - pub fn get_coo(&self) -> Result<&CooMatrix, StrError> { + pub fn get_coo(&self) -> Result<&NumCooMatrix, StrError> { match &self.coo { Some(coo) => Ok(coo), None => Err("COO matrix is not available"), @@ -316,17 +306,49 @@ impl SparseMatrix { } /// Returns a read-write access to the COO matrix, if available - pub fn get_coo_mut(&mut self) -> Result<&mut CooMatrix, StrError> { + pub fn get_coo_mut(&mut self) -> Result<&mut NumCooMatrix, StrError> { match &mut self.coo { Some(coo) => Ok(coo), None => Err("COO matrix is not available"), } } + /// Assigns this matrix to the values of another matrix (scaled) + /// + /// Performs: + /// + /// ```text + /// this = α · other + /// ``` + /// + /// **Warning:** make sure to allocate `max_nnz ≥ nnz(other)`. + pub fn assign(&mut self, alpha: T, other: &NumSparseMatrix) -> Result<(), StrError> { + match &mut self.coo { + Some(coo) => coo.assign(alpha, other.get_coo()?), + None => Err("COO matrix is not available to perform assignment"), + } + } + + /// Augments this matrix with the entries of another matrix (scaled) + /// + /// Effectively, performs: + /// + /// ```text + /// this += α · other + /// ``` + /// + /// **Warning:** make sure to allocate `max_nnz ≥ nnz(this) + nnz(other)`. + pub fn augment(&mut self, alpha: T, other: &NumSparseMatrix) -> Result<(), StrError> { + match &mut self.coo { + Some(coo) => coo.augment(alpha, other.get_coo()?), + None => Err("COO matrix is not available to augment"), + } + } + // CSC ------------------------------------------------------------------------ /// Returns a read-only access to the CSC matrix, if available - pub fn get_csc(&self) -> Result<&CscMatrix, StrError> { + pub fn get_csc(&self) -> Result<&NumCscMatrix, StrError> { match &self.csc { Some(csc) => Ok(csc), None => Err("CSC matrix is not available"), @@ -334,7 +356,7 @@ impl SparseMatrix { } /// Returns a read-write access to the CSC matrix, if available - pub fn get_csc_mut(&mut self) -> Result<&mut CscMatrix, StrError> { + pub fn get_csc_mut(&mut self) -> Result<&mut NumCscMatrix, StrError> { match &mut self.csc { Some(csc) => Ok(csc), None => Err("CSC matrix is not available"), @@ -347,15 +369,15 @@ impl SparseMatrix { /// automatically get the converted CSC matrix. /// /// **Priority**: COO -> CSC - pub fn get_csc_or_from_coo(&mut self) -> Result<&CscMatrix, StrError> { + pub fn get_csc_or_from_coo(&mut self) -> Result<&NumCscMatrix, StrError> { match &self.coo { Some(coo) => match &mut self.csc { Some(csc) => { - csc.update_from_coo(coo)?; + csc.update_from_coo(coo).unwrap(); // unwrap because csc cannot be wrong (created here) Ok(self.csc.as_ref().unwrap()) } None => { - self.csc = Some(CscMatrix::from_coo(coo)?); + self.csc = Some(NumCscMatrix::from_coo(coo)?); Ok(self.csc.as_ref().unwrap()) } }, @@ -369,7 +391,7 @@ impl SparseMatrix { // CSR ------------------------------------------------------------------------ /// Returns a read-only access to the CSR matrix, if available - pub fn get_csr(&self) -> Result<&CsrMatrix, StrError> { + pub fn get_csr(&self) -> Result<&NumCsrMatrix, StrError> { match &self.csr { Some(csr) => Ok(csr), None => Err("CSR matrix is not available"), @@ -377,7 +399,7 @@ impl SparseMatrix { } /// Returns a read-write access to the CSR matrix, if available - pub fn get_csr_mut(&mut self) -> Result<&mut CsrMatrix, StrError> { + pub fn get_csr_mut(&mut self) -> Result<&mut NumCsrMatrix, StrError> { match &mut self.csr { Some(csr) => Ok(csr), None => Err("CSR matrix is not available"), @@ -390,15 +412,15 @@ impl SparseMatrix { /// automatically get the converted CSR matrix. /// /// **Priority**: COO -> CSR - pub fn get_csr_or_from_coo(&mut self) -> Result<&CsrMatrix, StrError> { + pub fn get_csr_or_from_coo(&mut self) -> Result<&NumCsrMatrix, StrError> { match &self.coo { Some(coo) => match &mut self.csr { Some(csr) => { - csr.update_from_coo(coo)?; + csr.update_from_coo(coo).unwrap(); // unwrap because csr cannot be wrong (created here) Ok(self.csr.as_ref().unwrap()) } None => { - self.csr = Some(CsrMatrix::from_coo(coo)?); + self.csr = Some(NumCsrMatrix::from_coo(coo)?); Ok(self.csr.as_ref().unwrap()) } }, @@ -414,65 +436,60 @@ impl SparseMatrix { #[cfg(test)] mod tests { - use super::SparseMatrix; - use crate::Samples; + use super::NumSparseMatrix; + use crate::{Samples, Symmetry}; use russell_lab::{vec_approx_eq, Matrix, Vector}; - use std::fs; #[test] fn new_functions_work() { // COO - SparseMatrix::new_coo(1, 1, 1, None, false).unwrap(); + NumSparseMatrix::::new_coo(1, 1, 1, None).unwrap(); assert_eq!( - SparseMatrix::new_coo(0, 1, 1, None, false).err(), + NumSparseMatrix::::new_coo(0, 1, 1, None).err(), Some("nrow must be ≥ 1") ); // CSC - SparseMatrix::new_csc(1, 1, vec![0, 1], vec![0], vec![0.0], None).unwrap(); + NumSparseMatrix::::new_csc(1, 1, vec![0, 1], vec![0], vec![0.0], None).unwrap(); assert_eq!( - SparseMatrix::new_csc(0, 1, vec![0, 1], vec![0], vec![0.0], None).err(), + NumSparseMatrix::::new_csc(0, 1, vec![0, 1], vec![0], vec![0.0], None).err(), Some("nrow must be ≥ 1") ); // CSR - SparseMatrix::new_csr(1, 1, vec![0, 1], vec![0], vec![0.0], None).unwrap(); + NumSparseMatrix::::new_csr(1, 1, vec![0, 1], vec![0], vec![0.0], None).unwrap(); assert_eq!( - SparseMatrix::new_csr(0, 1, vec![0, 1], vec![0], vec![0.0], None).err(), + NumSparseMatrix::::new_csr(0, 1, vec![0, 1], vec![0], vec![0.0], None).err(), Some("nrow must be ≥ 1") ); } #[test] fn getters_work() { - // un-mutable - // ┌ ┐ - // │ 10 20 │ - // └ ┘ - let (coo, csc, csr, _) = Samples::rectangular_1x2(false, false, false); + // test matrices + let (coo, csc, csr, _) = Samples::rectangular_1x2(false, false); let mut a = Matrix::new(1, 2); let x = Vector::from(&[2.0, 1.0]); let mut wrong = Vector::new(2); // COO - let coo_mat = SparseMatrix::from_coo(coo); - assert_eq!(coo_mat.get_info(), (1, 2, 2, None)); - assert_eq!(coo_mat.get_max_abs_value(), 20.0); - assert_eq!(coo_mat.get_coo().unwrap().get_info(), (1, 2, 2, None)); + let coo_mat = NumSparseMatrix::::from_coo(coo); + assert_eq!(coo_mat.get_info(), (1, 2, 2, Symmetry::No)); + assert_eq!(coo_mat.get_coo().unwrap().get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(coo_mat.get_csc().err(), Some("CSC matrix is not available")); assert_eq!(coo_mat.get_csr().err(), Some("CSR matrix is not available")); + assert_eq!(coo_mat.get_values(), &[10.0, 20.0]); // CSC - let csc_mat = SparseMatrix::from_csc(csc); - assert_eq!(csc_mat.get_info(), (1, 2, 2, None)); - assert_eq!(csc_mat.get_max_abs_value(), 20.0); - assert_eq!(csc_mat.get_csc().unwrap().get_info(), (1, 2, 2, None)); + let csc_mat = NumSparseMatrix::::from_csc(csc); + assert_eq!(csc_mat.get_info(), (1, 2, 2, Symmetry::No)); + assert_eq!(csc_mat.get_csc().unwrap().get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(csc_mat.get_coo().err(), Some("COO matrix is not available")); assert_eq!(csc_mat.get_csr().err(), Some("CSR matrix is not available")); + assert_eq!(csc_mat.get_values(), &[10.0, 20.0]); // CSR - let csr_mat = SparseMatrix::from_csr(csr); - assert_eq!(csr_mat.get_info(), (1, 2, 2, None)); - assert_eq!(csr_mat.get_max_abs_value(), 20.0); - assert_eq!(csr_mat.get_csr().unwrap().get_info(), (1, 2, 2, None)); + let csr_mat = NumSparseMatrix::::from_csr(csr); + assert_eq!(csr_mat.get_info(), (1, 2, 2, Symmetry::No)); + assert_eq!(csr_mat.get_csr().unwrap().get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(csr_mat.get_csc().err(), Some("CSC matrix is not available")); assert_eq!(csr_mat.get_coo().err(), Some("COO matrix is not available")); - + assert_eq!(csr_mat.get_values(), &[10.0, 20.0]); // COO, CSC, CSR let mut ax = Vector::new(1); for mat in [&coo_mat, &csc_mat, &csr_mat] { @@ -480,7 +497,7 @@ mod tests { vec_approx_eq(&ax.as_data(), &[80.0], 1e-15); assert_eq!( mat.mat_vec_mul(&mut wrong, 1.0, &x).err(), - Some("v.ndim must equal nrow") + Some("v vector is incompatible") ); mat.to_dense(&mut a).unwrap(); assert_eq!(a.dims(), (1, 2)); @@ -495,22 +512,31 @@ mod tests { #[test] fn setters_work() { - // mutable - // ┌ ┐ - // │ 10 20 │ - // └ ┘ - let (coo, csc, csr, _) = Samples::rectangular_1x2(false, false, false); + // test matrices + let (coo, csc, csr, _) = Samples::rectangular_1x2(false, false); + let mut other = NumSparseMatrix::::new_coo(1, 1, 1, None).unwrap(); + let mut wrong = NumSparseMatrix::::new_coo(1, 1, 3, None).unwrap(); + other.put(0, 0, 2.0).unwrap(); + wrong.put(0, 0, 1.0).unwrap(); + wrong.put(0, 0, 2.0).unwrap(); + wrong.put(0, 0, 3.0).unwrap(); // COO - let mut coo_mat = SparseMatrix::from_coo(coo); - assert_eq!(coo_mat.get_coo_mut().unwrap().get_info(), (1, 2, 2, None)); + let mut coo_mat = NumSparseMatrix::::from_coo(coo); + assert_eq!(coo_mat.get_coo_mut().unwrap().get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(coo_mat.get_csc_mut().err(), Some("CSC matrix is not available")); assert_eq!(coo_mat.get_csr_mut().err(), Some("CSR matrix is not available")); + let mut empty = NumSparseMatrix::::new_coo(1, 1, 1, None).unwrap(); + assert_eq!(empty.get_csc_or_from_coo().err(), Some("COO to CSC requires nnz > 0")); + assert_eq!(empty.get_csr_or_from_coo().err(), Some("COO to CSR requires nnz > 0")); // CSC - let mut csc_mat = SparseMatrix::from_csc(csc); - assert_eq!(csc_mat.get_csc_mut().unwrap().get_info(), (1, 2, 2, None)); + let mut csc_mat = NumSparseMatrix::::from_csc(csc); + assert_eq!(csc_mat.get_csc_mut().unwrap().get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(csc_mat.get_coo_mut().err(), Some("COO matrix is not available")); assert_eq!(csc_mat.get_csr_mut().err(), Some("CSR matrix is not available")); - assert_eq!(csc_mat.get_csc_or_from_coo().unwrap().get_info(), (1, 2, 2, None)); + assert_eq!( + csc_mat.get_csc_or_from_coo().unwrap().get_info(), + (1, 2, 2, Symmetry::No) + ); assert_eq!( csc_mat.get_csr_or_from_coo().err(), Some("CSR is not available and COO matrix is not available to convert to CSR") @@ -523,12 +549,23 @@ mod tests { csc_mat.reset().err(), Some("COO matrix is not available to reset nnz counter") ); + assert_eq!( + csc_mat.assign(4.0, &other).err(), + Some("COO matrix is not available to perform assignment") + ); + assert_eq!( + csc_mat.augment(4.0, &other).err(), + Some("COO matrix is not available to augment") + ); // CSR - let mut csr_mat = SparseMatrix::from_csr(csr); - assert_eq!(csr_mat.get_csr_mut().unwrap().get_info(), (1, 2, 2, None)); + let mut csr_mat = NumSparseMatrix::::from_csr(csr); + assert_eq!(csr_mat.get_csr_mut().unwrap().get_info(), (1, 2, 2, Symmetry::No)); assert_eq!(csr_mat.get_csc_mut().err(), Some("CSC matrix is not available")); assert_eq!(csr_mat.get_coo_mut().err(), Some("COO matrix is not available")); - assert_eq!(csr_mat.get_csr_or_from_coo().unwrap().get_info(), (1, 2, 2, None)); + assert_eq!( + csr_mat.get_csr_or_from_coo().unwrap().get_info(), + (1, 2, 2, Symmetry::No) + ); assert_eq!( csr_mat.get_csc_or_from_coo().err(), Some("CSC is not available and COO matrix is not available to convert to CSC") @@ -541,9 +578,16 @@ mod tests { csr_mat.reset().err(), Some("COO matrix is not available to reset nnz counter") ); - + assert_eq!( + csr_mat.assign(4.0, &other).err(), + Some("COO matrix is not available to perform assignment") + ); + assert_eq!( + csr_mat.augment(4.0, &other).err(), + Some("COO matrix is not available to augment") + ); // COO - let mut coo = SparseMatrix::new_coo(2, 2, 1, None, false).unwrap(); + let mut coo = NumSparseMatrix::::new_coo(2, 2, 1, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); assert_eq!( coo.put(1, 1, 2.0).err(), @@ -551,94 +595,88 @@ mod tests { ); coo.reset().unwrap(); coo.put(1, 1, 2.0).unwrap(); - } - - #[test] - fn write_matrix_market_works() { - // 2 3 . . . - // 3 . 4 . 6 - // . -1 -3 2 . - // . . 1 . . - // . 4 2 . 1 - let (coo, csc, csr, _) = Samples::umfpack_unsymmetric_5x5(false); - - // COO - let mut mat = SparseMatrix::from_coo(coo); - let full_path = "/tmp/russell_sparse/test_write_matrix_market_coo.mtx"; - mat.write_matrix_market(full_path, false).unwrap(); - let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); + // COO (assign) + let mut this = NumSparseMatrix::::new_coo(1, 1, 1, None).unwrap(); + this.put(0, 0, 8000.0).unwrap(); + this.assign(4.0, &other).unwrap(); assert_eq!( - contents, - "%%MatrixMarket matrix coordinate real general\n\ - 5 5 12\n\ - 1 1 2.0\n\ - 2 1 3.0\n\ - 1 2 3.0\n\ - 3 2 -1.0\n\ - 5 2 4.0\n\ - 2 3 4.0\n\ - 3 3 -3.0\n\ - 4 3 1.0\n\ - 5 3 2.0\n\ - 3 4 2.0\n\ - 2 5 6.0\n\ - 5 5 1.0\n" + format!("{}", this.as_dense()), + "┌ ┐\n\ + │ 8 │\n\ + └ ┘" ); - - // CSC - let mut mat = SparseMatrix::from_csc(csc); - let full_path = "/tmp/russell_sparse/test_write_matrix_market_csc.mtx"; - mat.write_matrix_market(full_path, false).unwrap(); - let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); assert_eq!( - contents, - "%%MatrixMarket matrix coordinate real general\n\ - 5 5 12\n\ - 1 1 2.0\n\ - 2 1 3.0\n\ - 1 2 3.0\n\ - 3 2 -1.0\n\ - 5 2 4.0\n\ - 2 3 4.0\n\ - 3 3 -3.0\n\ - 4 3 1.0\n\ - 5 3 2.0\n\ - 3 4 2.0\n\ - 2 5 6.0\n\ - 5 5 1.0\n" + this.assign(2.0, &wrong).err(), + Some("COO matrix: max number of items has been reached") ); - - // CSR - let mut mat = SparseMatrix::from_csr(csr); - let full_path = "/tmp/russell_sparse/test_write_matrix_market_csr.mtx"; - mat.write_matrix_market(full_path, false).unwrap(); - let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); + assert_eq!(this.assign(2.0, &csc_mat).err(), Some("COO matrix is not available")); + // COO (augment) + let mut this = NumSparseMatrix::::new_coo(1, 1, 1 + 1, None).unwrap(); + this.put(0, 0, 100.0).unwrap(); + this.augment(4.0, &other).unwrap(); assert_eq!( - contents, - "%%MatrixMarket matrix coordinate real general\n\ - 5 5 12\n\ - 1 1 2.0\n\ - 1 2 3.0\n\ - 2 1 3.0\n\ - 2 3 4.0\n\ - 2 5 6.0\n\ - 3 2 -1.0\n\ - 3 3 -3.0\n\ - 3 4 2.0\n\ - 4 3 1.0\n\ - 5 2 4.0\n\ - 5 3 2.0\n\ - 5 5 1.0\n" + format!("{}", this.as_dense()), + "┌ ┐\n\ + │ 108 │\n\ + └ ┘" ); + assert_eq!(this.augment(2.0, &csc_mat).err(), Some("COO matrix is not available")); + } + + #[test] + fn get_csc_or_from_coo_works() { + // ┌ ┐ + // │ 10 20 │ + // └ ┘ + let (coo, _, _, _) = Samples::rectangular_1x2(false, false); + let mut mat = NumSparseMatrix::::from_coo(coo); + let csc = mat.get_csc_or_from_coo().unwrap(); // will create a new csc + assert_eq!(csc.get_values(), &[10.0, 20.0]); + let coo_internal = mat.get_coo_mut().unwrap(); + let source = coo_internal.get_values_mut(); + source[0] = 30.0; // change a value + let csc = mat.get_csc_or_from_coo().unwrap(); // will update existent csc + assert_eq!(csc.get_values(), &[30.0, 20.0]); + } + + #[test] + fn get_csr_or_from_coo_works() { + // ┌ ┐ + // │ 10 20 │ + // └ ┘ + let (coo, _, _, _) = Samples::rectangular_1x2(false, false); + let mut mat = NumSparseMatrix::::from_coo(coo); + let csr = mat.get_csr_or_from_coo().unwrap(); // will create a new csr + assert_eq!(csr.get_values(), &[10.0, 20.0]); + let coo_internal = mat.get_coo_mut().unwrap(); + let source = coo_internal.get_values_mut(); + source[0] = 30.0; // change a value + let csr = mat.get_csr_or_from_coo().unwrap(); // will update existent csr + assert_eq!(csr.get_values(), &[30.0, 20.0]); } #[test] - fn clone_works() { - let (coo, _, _, _) = Samples::tiny_1x1(false); - let mat = SparseMatrix::from_coo(coo); + fn derive_methods_work() { + let (coo, _, _, _) = Samples::tiny_1x1(); + let (nrow, ncol, nnz, symmetry) = coo.get_info(); + let mat = NumSparseMatrix::::from_coo(coo); let mut clone = mat.clone(); clone.get_coo_mut().unwrap().values[0] *= 2.0; assert_eq!(mat.get_coo().unwrap().values[0], 123.0); assert_eq!(clone.get_coo().unwrap().values[0], 246.0); + assert!(format!("{:?}", mat).len() > 0); + let json = serde_json::to_string(&mat).unwrap(); + assert_eq!( + json, + r#"{"coo":{"symmetry":"No","nrow":1,"ncol":1,"nnz":1,"max_nnz":1,"indices_i":[0],"indices_j":[0],"values":[123.0]},"csc":null,"csr":null}"# + ); + let from_json: NumSparseMatrix = serde_json::from_str(&json).unwrap(); + let (json_nrow, json_ncol, json_nnz, json_symmetry) = from_json.get_coo().unwrap().get_info(); + assert_eq!(json_symmetry, symmetry); + assert_eq!(json_nrow, nrow); + assert_eq!(json_ncol, ncol); + assert_eq!(json_nnz, nnz); + assert!(from_json.csc.is_none()); + assert!(from_json.csr.is_none()); } } diff --git a/russell_sparse/src/stats_lin_sol.rs b/russell_sparse/src/stats_lin_sol.rs index 98f22e65..8cad573f 100644 --- a/russell_sparse/src/stats_lin_sol.rs +++ b/russell_sparse/src/stats_lin_sol.rs @@ -49,7 +49,8 @@ pub struct StatsLinSolOutput { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct StatsLinSolDeterminant { // det = mantissa * pow(base, exponent) - pub mantissa: f64, + pub mantissa_real: f64, // the real part of the mantissa + pub mantissa_imag: f64, // the imaginary part of the mantissa (if complex) pub base: f64, pub exponent: f64, } @@ -127,7 +128,8 @@ impl StatsLinSol { umfpack_rcond_estimate: 0.0, }, determinant: StatsLinSolDeterminant { - mantissa: 0.0, + mantissa_real: 0.0, + mantissa_imag: 0.0, base: 0.0, exponent: 0.0, }, diff --git a/russell_sparse/src/verify_lin_sys.rs b/russell_sparse/src/verify_lin_sys.rs index a60cb2e9..dca60b51 100644 --- a/russell_sparse/src/verify_lin_sys.rs +++ b/russell_sparse/src/verify_lin_sys.rs @@ -1,6 +1,8 @@ -use super::SparseMatrix; +use super::{ComplexSparseMatrix, SparseMatrix}; use crate::StrError; -use russell_lab::{vec_norm, vec_update, Norm, Vector}; +use num_complex::Complex64; +use russell_lab::{complex_vec_norm, complex_vec_update, cpx, ComplexVector}; +use russell_lab::{find_index_abs_max, vec_norm, vec_update, Norm, Vector}; use serde::{Deserialize, Serialize}; /// Verifies the linear system a ⋅ x = rhs @@ -13,7 +15,7 @@ pub struct VerifyLinSys { } impl VerifyLinSys { - /// Creates a new verification dataset + /// Computes verification data for a sparse system /// /// ```text /// diff : = | a ⋅ x - rhs| @@ -30,7 +32,7 @@ impl VerifyLinSys { /// fn main() -> Result<(), StrError> { /// // set sparse matrix (3 x 3) with 4 non-zeros /// let (nrow, ncol, nnz) = (3, 3, 4); - /// let mut coo = SparseMatrix::new_coo(nrow, ncol, nnz, None, false)?; + /// let mut coo = SparseMatrix::new_coo(nrow, ncol, nnz, None)?; /// coo.put(0, 0, 1.0)?; /// coo.put(0, 2, 4.0)?; /// coo.put(1, 1, 2.0)?; @@ -48,7 +50,7 @@ impl VerifyLinSys { /// // verify lin-sys /// let x = Vector::from(&[1.0, 1.0, 1.0]); /// let rhs = Vector::from(&[5.0, 2.0, 3.0]); - /// let verify = VerifyLinSys::new(&coo, &x, &rhs)?; + /// let verify = VerifyLinSys::from(&coo, &x, &rhs)?; /// assert_eq!(verify.max_abs_a, 4.0); /// assert_eq!(verify.max_abs_ax, 5.0); /// assert_eq!(verify.max_abs_diff, 0.0); @@ -56,7 +58,7 @@ impl VerifyLinSys { /// Ok(()) /// } /// ``` - pub fn new(mat: &SparseMatrix, x: &Vector, rhs: &Vector) -> Result { + pub fn from(mat: &SparseMatrix, x: &Vector, rhs: &Vector) -> Result { let (nrow, ncol, _, _) = mat.get_info(); if x.dim() != ncol { return Err("x.dim() must be equal to ncol"); @@ -66,7 +68,12 @@ impl VerifyLinSys { } // compute max_abs_a - let max_abs_a = mat.get_max_abs_value(); + let values = mat.get_values(); + if values.len() < 1 { + return Err("matrix is empty"); + } + let idx = find_index_abs_max(values); + let max_abs_a = f64::abs(values[idx as usize]); // compute max_abs_ax let mut ax = Vector::new(nrow); @@ -88,6 +95,56 @@ impl VerifyLinSys { relative_error, }) } + + /// Computes verification data for a complex sparse system + /// + /// ```text + /// diff : = | a ⋅ x - rhs| + /// (m,n) (n) (m) + /// ``` + pub fn from_complex(mat: &ComplexSparseMatrix, x: &ComplexVector, rhs: &ComplexVector) -> Result { + let (nrow, ncol, _, _) = mat.get_info(); + if x.dim() != ncol { + return Err("x.dim() must be equal to ncol"); + } + if rhs.dim() != nrow { + return Err("rhs.dim() must be equal to nrow"); + } + + // compute max_abs_a + let values = mat.get_values(); + if values.len() < 1 { + return Err("matrix is empty"); + } + let nnz = values.len(); + let mut max_abs_a = 0.0; + for k in 0..nnz { + let abs = values[k].norm(); + if abs > max_abs_a { + max_abs_a = abs; + } + } + + // compute max_abs_ax + let mut ax = ComplexVector::new(nrow); + mat.mat_vec_mul(&mut ax, cpx!(1.0, 0.0), &x).unwrap(); // unwrap bc already checked dims + let max_abs_ax = complex_vec_norm(&ax, Norm::Max); + + // compute max_abs_diff + complex_vec_update(&mut ax, cpx!(-1.0, 0.0), &rhs).unwrap(); // ax := ax - rhs + let max_abs_diff = complex_vec_norm(&ax, Norm::Max); + + // compute relative_error + let relative_error = max_abs_diff / (max_abs_a + 1.0); + + // results + Ok(VerifyLinSys { + max_abs_a, + max_abs_ax, + max_abs_diff, + relative_error, + }) + } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -95,23 +152,43 @@ impl VerifyLinSys { #[cfg(test)] mod tests { use super::VerifyLinSys; - use crate::{Samples, SparseMatrix}; - use russell_lab::{approx_eq, Vector}; + use crate::{ComplexSparseMatrix, Samples, SparseMatrix}; + use num_complex::Complex64; + use russell_lab::{approx_eq, cpx, ComplexVector, Vector}; #[test] - fn new_captures_errors() { - let coo = SparseMatrix::new_coo(2, 1, 1, None, false).unwrap(); + fn from_captures_errors() { + // real + let coo = SparseMatrix::new_coo(2, 1, 1, None).unwrap(); let x = Vector::new(1); let rhs = Vector::new(2); - assert_eq!(VerifyLinSys::new(&coo, &x, &rhs).err(), None); + assert_eq!(VerifyLinSys::from(&coo, &x, &rhs).err(), Some("matrix is empty")); let x_wrong = Vector::new(2); let rhs_wrong = Vector::new(1); assert_eq!( - VerifyLinSys::new(&coo, &x_wrong, &rhs).err(), + VerifyLinSys::from(&coo, &x_wrong, &rhs).err(), + Some("x.dim() must be equal to ncol") + ); + assert_eq!( + VerifyLinSys::from(&coo, &x, &rhs_wrong).err(), + Some("rhs.dim() must be equal to nrow") + ); + // complex + let coo = ComplexSparseMatrix::new_coo(2, 1, 1, None).unwrap(); + let x = ComplexVector::new(1); + let rhs = ComplexVector::new(2); + assert_eq!( + VerifyLinSys::from_complex(&coo, &x, &rhs).err(), + Some("matrix is empty") + ); + let x_wrong = ComplexVector::new(2); + let rhs_wrong = ComplexVector::new(1); + assert_eq!( + VerifyLinSys::from_complex(&coo, &x_wrong, &rhs).err(), Some("x.dim() must be equal to ncol") ); assert_eq!( - VerifyLinSys::new(&coo, &x, &rhs_wrong).err(), + VerifyLinSys::from_complex(&coo, &x, &rhs_wrong).err(), Some("rhs.dim() must be equal to nrow") ); } @@ -121,7 +198,7 @@ mod tests { // 1 3 -2 // 3 5 6 // 2 4 3 - let mut coo = SparseMatrix::new_coo(3, 3, 9, None, false).unwrap(); + let mut coo = SparseMatrix::new_coo(3, 3, 9, None).unwrap(); coo.put(0, 0, 1.0).unwrap(); coo.put(0, 1, 3.0).unwrap(); coo.put(0, 2, -2.0).unwrap(); @@ -133,7 +210,7 @@ mod tests { coo.put(2, 2, 3.0).unwrap(); let x = Vector::from(&[-15.0, 8.0, 2.0]); let rhs = Vector::from(&[5.0, 7.0, 8.0]); - let verify = VerifyLinSys::new(&coo, &x, &rhs).unwrap(); + let verify = VerifyLinSys::from(&coo, &x, &rhs).unwrap(); assert_eq!(verify.max_abs_a, 6.0); assert_eq!(verify.max_abs_ax, 8.0); assert_eq!(verify.max_abs_diff, 0.0); @@ -153,13 +230,13 @@ mod tests { // COO let mat = SparseMatrix::from_coo(coo); - let verify = VerifyLinSys::new(&mat, &x, &rhs).unwrap(); + let verify = VerifyLinSys::from(&mat, &x, &rhs).unwrap(); assert_eq!(verify.max_abs_a, 15.0); assert_eq!(verify.max_abs_ax, 12.0); assert_eq!(verify.max_abs_diff, 12.0); approx_eq(verify.relative_error, 12.0 / (15.0 + 1.0), 1e-15); - let verify = VerifyLinSys::new(&mat, &x, &Vector::from(a_times_x)).unwrap(); + let verify = VerifyLinSys::from(&mat, &x, &Vector::from(a_times_x)).unwrap(); assert_eq!(verify.max_abs_a, 15.0); assert_eq!(verify.max_abs_ax, 12.0); assert_eq!(verify.max_abs_diff, 0.0); @@ -167,7 +244,7 @@ mod tests { // CSC let mat = SparseMatrix::from_csc(csc); - let verify = VerifyLinSys::new(&mat, &x, &rhs).unwrap(); + let verify = VerifyLinSys::from(&mat, &x, &rhs).unwrap(); assert_eq!(verify.max_abs_a, 15.0); assert_eq!(verify.max_abs_ax, 12.0); assert_eq!(verify.max_abs_diff, 12.0); @@ -175,10 +252,37 @@ mod tests { // CSR let mat = SparseMatrix::from_csr(csr); - let verify = VerifyLinSys::new(&mat, &x, &rhs).unwrap(); + let verify = VerifyLinSys::from(&mat, &x, &rhs).unwrap(); assert_eq!(verify.max_abs_a, 15.0); assert_eq!(verify.max_abs_ax, 12.0); assert_eq!(verify.max_abs_diff, 12.0); approx_eq(verify.relative_error, 12.0 / (15.0 + 1.0), 1e-15); } + + #[test] + fn new_complex_matrix_works() { + // 4+4i . 2+2i + // . 1 3+3i + // . 5+5i 1+1i + // 1 . . + let (coo, _, _, _) = Samples::complex_rectangular_4x3(); + let mat = ComplexSparseMatrix::from_coo(coo); + let x = ComplexVector::from(&[cpx!(1.0, 2.0), cpx!(2.0, -1.0), cpx!(0.0, 1.0)]); + + // zero error + let rhs = ComplexVector::from(&[cpx!(-6.0, 14.0), cpx!(-1.0, 2.0), cpx!(14.0, 6.0), cpx!(1.0, 2.0)]); + let verify = VerifyLinSys::from_complex(&mat, &x, &rhs).unwrap(); + approx_eq(verify.max_abs_a, 7.0710678118654755, 1e-15); + approx_eq(verify.max_abs_ax, 15.231546211727817, 1e-15); + approx_eq(verify.max_abs_diff, 0.0, 1e-15); + approx_eq(verify.relative_error, 0.0, 1e-15); + + // with error + let rhs = ComplexVector::from(&[cpx!(-6.0, 14.0), cpx!(-1.0, 2.0), cpx!(14.0, 6.0), cpx!(1.0, 0.0)]); + let verify = VerifyLinSys::from_complex(&mat, &x, &rhs).unwrap(); + approx_eq(verify.max_abs_a, 7.0710678118654755, 1e-15); + approx_eq(verify.max_abs_ax, 15.231546211727817, 1e-15); + approx_eq(verify.max_abs_diff, 2.0, 1e-15); + approx_eq(verify.relative_error, 2.0 / (7.0710678118654755 + 1.0), 1e-15); + } } diff --git a/russell_sparse/src/write_matrix_market.rs b/russell_sparse/src/write_matrix_market.rs new file mode 100644 index 00000000..cacd2580 --- /dev/null +++ b/russell_sparse/src/write_matrix_market.rs @@ -0,0 +1,247 @@ +use super::{CscMatrix, CsrMatrix, Symmetry}; +use crate::StrError; +use std::ffi::OsStr; +use std::fmt::Write; +use std::fs::{self, File}; +use std::io::Write as IoWrite; +use std::path::Path; + +/// Writes a MatrixMarket file from a CooMatrix +/// +/// # Input +/// +/// * `full_path` -- may be a String, &str, or Path +/// * `vismatrix` -- generate a SMAT file for Vismatrix instead of a MatrixMarket +/// +/// **Note:** The vismatrix format is is similar to the MatrixMarket format +/// without the header, and the indices start at zero. +/// +/// # References +/// +/// * MatrixMarket: +/// * Vismatrix: +pub fn csc_write_matrix_market

(mat: &CscMatrix, full_path: &P, vismatrix: bool) -> Result<(), StrError> +where + P: AsRef + ?Sized, +{ + // output buffer + let mut buffer = String::new(); + + // handle one-based indexing + let d = if vismatrix { 0 } else { 1 }; + + // info + let (nrow, ncol, nnz, symmetry) = mat.get_info(); + + // write header + if !vismatrix { + if symmetry == Symmetry::No { + write!(&mut buffer, "%%MatrixMarket matrix coordinate real general\n").unwrap(); + } else { + write!(&mut buffer, "%%MatrixMarket matrix coordinate real symmetric\n").unwrap(); + } + } + + // write dimensions + write!(&mut buffer, "{} {} {}\n", nrow, ncol, nnz).unwrap(); + + // access data + let col_pointers = mat.get_col_pointers(); + let row_indices = mat.get_row_indices(); + let values = mat.get_values(); + + // write triplets + for j in 0..ncol { + for p in col_pointers[j]..col_pointers[j + 1] { + let i = row_indices[p as usize] as usize; + let aij = values[p as usize]; + write!(&mut buffer, "{} {} {:?}\n", i + d, j + d, aij).unwrap(); + } + } + + // create directory + let path = Path::new(full_path); + if let Some(p) = path.parent() { + fs::create_dir_all(p).map_err(|_| "cannot create directory")?; + } + + // write file + let mut file = File::create(path).map_err(|_| "cannot create file")?; + file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; + + // force sync + file.sync_all().map_err(|_| "cannot sync file")?; + Ok(()) +} + +/// Writes a MatrixMarket file from a CooMatrix +/// +/// # Input +/// +/// * `full_path` -- may be a String, &str, or Path +/// * `vismatrix` -- generate a SMAT file for Vismatrix instead of a MatrixMarket +/// +/// **Note:** The vismatrix format is is similar to the MatrixMarket format +/// without the header, and the indices start at zero. +/// +/// # References +/// +/// * MatrixMarket: +/// * Vismatrix: +pub fn csr_write_matrix_market

(mat: &CsrMatrix, full_path: &P, vismatrix: bool) -> Result<(), StrError> +where + P: AsRef + ?Sized, +{ + // output buffer + let mut buffer = String::new(); + + // handle one-based indexing + let d = if vismatrix { 0 } else { 1 }; + + // info + let (nrow, ncol, nnz, symmetry) = mat.get_info(); + + // write header + if !vismatrix { + if symmetry == Symmetry::No { + write!(&mut buffer, "%%MatrixMarket matrix coordinate real general\n").unwrap(); + } else { + write!(&mut buffer, "%%MatrixMarket matrix coordinate real symmetric\n").unwrap(); + } + } + + // write dimensions + write!(&mut buffer, "{} {} {}\n", nrow, ncol, nnz).unwrap(); + + // access data + let row_pointers = mat.get_row_pointers(); + let col_indices = mat.get_col_indices(); + let values = mat.get_values(); + + // write triplets + for i in 0..nrow { + for p in row_pointers[i]..row_pointers[i + 1] { + let j = col_indices[p as usize] as usize; + let aij = values[p as usize]; + println!("{} {} {:?}", i, j, aij); + write!(&mut buffer, "{} {} {:?}\n", i + d, j + d, aij).unwrap(); + } + } + + // create directory + let path = Path::new(full_path); + if let Some(p) = path.parent() { + fs::create_dir_all(p).map_err(|_| "cannot create directory")?; + } + + // write file + let mut file = File::create(path).map_err(|_| "cannot create file")?; + file.write_all(buffer.as_bytes()).map_err(|_| "cannot write file")?; + + // force sync + file.sync_all().map_err(|_| "cannot sync file")?; + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::Samples; + use std::fs; + + #[test] + fn csc_write_matrix_market_works() { + // 2 3 . . . + // 3 . 4 . 6 + // . -1 -3 2 . + // . . 1 . . + // . 4 2 . 1 + let (_, csc, _, _) = Samples::umfpack_unsymmetric_5x5(); + let full_path = "/tmp/russell_sparse/test_write_matrix_market_csc.mtx"; + csc_write_matrix_market(&csc, full_path, false).unwrap(); + let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); + assert_eq!( + contents, + "%%MatrixMarket matrix coordinate real general\n\ + 5 5 12\n\ + 1 1 2.0\n\ + 2 1 3.0\n\ + 1 2 3.0\n\ + 3 2 -1.0\n\ + 5 2 4.0\n\ + 2 3 4.0\n\ + 3 3 -3.0\n\ + 4 3 1.0\n\ + 5 3 2.0\n\ + 3 4 2.0\n\ + 2 5 6.0\n\ + 5 5 1.0\n" + ); + // 2 -1 2 sym + // -1 2 -1 => -1 2 + // -1 2 -1 2 + let (_, csc, _, _) = Samples::positive_definite_3x3(); + let full_path = "/tmp/russell_sparse/test_write_matrix_market_csc.mtx"; + csc_write_matrix_market(&csc, full_path, false).unwrap(); + let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); + assert_eq!( + contents, + "%%MatrixMarket matrix coordinate real symmetric\n\ + 3 3 5\n\ + 1 1 2.0\n\ + 2 1 -1.0\n\ + 2 2 2.0\n\ + 3 2 -1.0\n\ + 3 3 2.0\n" + ); + } + + #[test] + fn csr_write_matrix_market_works() { + // 2 3 . . . + // 3 . 4 . 6 + // . -1 -3 2 . + // . . 1 . . + // . 4 2 . 1 + let (_, _, csr, _) = Samples::umfpack_unsymmetric_5x5(); + let full_path = "/tmp/russell_sparse/test_write_matrix_market_csr.mtx"; + csr_write_matrix_market(&csr, full_path, false).unwrap(); + let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); + assert_eq!( + contents, + "%%MatrixMarket matrix coordinate real general\n\ + 5 5 12\n\ + 1 1 2.0\n\ + 1 2 3.0\n\ + 2 1 3.0\n\ + 2 3 4.0\n\ + 2 5 6.0\n\ + 3 2 -1.0\n\ + 3 3 -3.0\n\ + 3 4 2.0\n\ + 4 3 1.0\n\ + 5 2 4.0\n\ + 5 3 2.0\n\ + 5 5 1.0\n" + ); + // 2 -1 2 sym + // -1 2 -1 => -1 2 + // -1 2 -1 2 + let (_, _, csr, _) = Samples::positive_definite_3x3(); + let full_path = "/tmp/russell_sparse/test_write_matrix_market_csr.mtx"; + csr_write_matrix_market(&csr, full_path, false).unwrap(); + let contents = fs::read_to_string(full_path).map_err(|_| "cannot open file").unwrap(); + assert_eq!( + contents, + "%%MatrixMarket matrix coordinate real symmetric\n\ + 3 3 5\n\ + 1 1 2.0\n\ + 2 1 -1.0\n\ + 2 2 2.0\n\ + 3 2 -1.0\n\ + 3 3 2.0\n" + ); + } +} diff --git a/russell_sparse/tests/test_complex_coo_matrix.rs b/russell_sparse/tests/test_complex_coo_matrix.rs new file mode 100644 index 00000000..b10abf12 --- /dev/null +++ b/russell_sparse/tests/test_complex_coo_matrix.rs @@ -0,0 +1,35 @@ +use num_complex::Complex64; +use russell_lab::*; +use russell_sparse::prelude::*; +use russell_sparse::StrError; + +#[test] +fn test_complex_coo_matrix() -> Result<(), StrError> { + let sym = Some(Symmetry::new_general_lower()); + let mut coo = ComplexCooMatrix::new(3, 3, 4, sym)?; + coo.put(0, 0, cpx!(1.0, 0.1))?; + coo.put(1, 0, cpx!(2.0, 0.2))?; + coo.put(1, 1, cpx!(3.0, 0.3))?; + coo.put(2, 1, cpx!(4.0, 0.4))?; + let mut a = ComplexMatrix::new(3, 3); + coo.to_dense(&mut a).unwrap(); + let correct = "┌ ┐\n\ + │ 1+0.1i 2+0.2i 0+0i │\n\ + │ 2+0.2i 3+0.3i 4+0.4i │\n\ + │ 0+0i 4+0.4i 0+0i │\n\ + └ ┘"; + assert_eq!(format!("{}", a), correct); + + let u = ComplexVector::from(&[cpx!(3.0, 0.0), cpx!(2.0, 2.0), cpx!(1.0, 0.0)]); + let mut v = ComplexVector::new(3); + coo.mat_vec_mul(&mut v, cpx!(1.0, 0.0), &u)?; + complex_vec_approx_eq(v.as_data(), &[cpx!(6.6, 4.7), cpx!(15.4, 7.6), cpx!(7.2, 8.8)], 1e-15); + + coo.mat_vec_mul(&mut v, cpx!(1.0, 2.0), &u)?; + complex_vec_approx_eq( + v.as_data(), + &[cpx!(-2.8, 17.9), cpx!(0.2, 38.4), cpx!(-10.4, 23.2)], + 1e-14, + ); + Ok(()) +} diff --git a/russell_sparse/tests/test_complex_mumps.rs b/russell_sparse/tests/test_complex_mumps.rs new file mode 100644 index 00000000..0f881481 --- /dev/null +++ b/russell_sparse/tests/test_complex_mumps.rs @@ -0,0 +1,34 @@ +use num_complex::Complex64; +use russell_lab::*; +use russell_sparse::prelude::*; +use russell_sparse::StrError; +use serial_test::serial; + +#[test] +#[serial] +fn test_complex_mumps() -> Result<(), StrError> { + let n = 10; + let d = (n as f64) / 10.0; + let mut coo = ComplexCooMatrix::new(n, n, n, None)?; + let mut x_correct = ComplexVector::new(n); + let mut rhs = ComplexVector::new(n); + for k in 0..n { + // put diagonal entries + let akk = cpx!(10.0 + (k as f64) * d, 10.0 - (k as f64) * d); + coo.put(k, k, akk)?; + // let the exact solution be k + 0.5i + x_correct[k] = cpx!(k as f64, 0.5); + // generate RHS to match solution + rhs[k] = akk * x_correct[k]; + } + // println!("a =\n{}", coo.as_dense()); + // println!("x =\n{}", x_correct); + // println!("b =\n{}", rhs); + let mut x = ComplexVector::new(n); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut solver = ComplexSolverMUMPS::new()?; + solver.factorize(&mut mat, None)?; + solver.solve(&mut x, &mut mat, &rhs, false)?; + complex_vec_approx_eq(x.as_data(), x_correct.as_data(), 1e-14); + Ok(()) +} diff --git a/russell_sparse/tests/test_complex_umfpack.rs b/russell_sparse/tests/test_complex_umfpack.rs new file mode 100644 index 00000000..07dc2063 --- /dev/null +++ b/russell_sparse/tests/test_complex_umfpack.rs @@ -0,0 +1,32 @@ +use num_complex::Complex64; +use russell_lab::*; +use russell_sparse::prelude::*; +use russell_sparse::StrError; + +#[test] +fn test_complex_umfpack() -> Result<(), StrError> { + let n = 10; + let d = (n as f64) / 10.0; + let mut coo = ComplexCooMatrix::new(n, n, n, None)?; + let mut x_correct = ComplexVector::new(n); + let mut rhs = ComplexVector::new(n); + for k in 0..n { + // put diagonal entries + let akk = cpx!(10.0 + (k as f64) * d, 10.0 - (k as f64) * d); + coo.put(k, k, akk)?; + // let the exact solution be k + 0.5i + x_correct[k] = cpx!(k as f64, 0.5); + // generate RHS to match solution + rhs[k] = akk * x_correct[k]; + } + // println!("a =\n{}", coo.as_dense()); + // println!("x =\n{}", x_correct); + // println!("b =\n{}", rhs); + let mut x = ComplexVector::new(n); + let mut mat = ComplexSparseMatrix::from_coo(coo); + let mut solver = ComplexSolverUMFPACK::new()?; + solver.factorize(&mut mat, None)?; + solver.solve(&mut x, &mut mat, &rhs, false)?; + complex_vec_approx_eq(x.as_data(), x_correct.as_data(), 1e-14); + Ok(()) +} diff --git a/russell_sparse/tests/test_mumps.rs b/russell_sparse/tests/test_mumps.rs new file mode 100644 index 00000000..58d770d1 --- /dev/null +++ b/russell_sparse/tests/test_mumps.rs @@ -0,0 +1,33 @@ +use russell_lab::*; +use russell_sparse::prelude::*; +use russell_sparse::StrError; +use serial_test::serial; + +#[test] +#[serial] +fn test_complex_umfpack() -> Result<(), StrError> { + let n = 10; + let d = (n as f64) / 10.0; + let mut coo = CooMatrix::new(n, n, n, None)?; + let mut x_correct = Vector::new(n); + let mut rhs = Vector::new(n); + for k in 0..n { + // put diagonal entries + let akk = 10.0 + (k as f64) * d; + coo.put(k, k, akk)?; + // let the exact solution be k + 0.5i + x_correct[k] = k as f64; + // generate RHS to match solution + rhs[k] = akk * x_correct[k]; + } + // println!("a =\n{}", coo.as_dense()); + // println!("x =\n{}", x_correct); + // println!("b =\n{}", rhs); + let mut x = Vector::new(n); + let mut mat = SparseMatrix::from_coo(coo); + let mut solver = SolverMUMPS::new()?; + solver.factorize(&mut mat, None)?; + solver.solve(&mut x, &mut mat, &rhs, false)?; + vec_approx_eq(x.as_data(), x_correct.as_data(), 1e-14); + Ok(()) +} diff --git a/russell_sparse/tests/test_nonlinear_system.rs b/russell_sparse/tests/test_nonlinear_system.rs index 845ec9f4..7312072d 100644 --- a/russell_sparse/tests/test_nonlinear_system.rs +++ b/russell_sparse/tests/test_nonlinear_system.rs @@ -67,7 +67,7 @@ fn check_jacobian() { } } let nnz = neq * neq; - let mut jj_tri = CooMatrix::new(neq, neq, nnz, None, false).unwrap(); + let mut jj_tri = CooMatrix::new(neq, neq, nnz, None).unwrap(); calc_jacobian(&mut jj_tri, &uu).unwrap(); let mut jj_ana = Matrix::new(neq, neq); jj_tri.to_dense(&mut jj_ana).unwrap(); @@ -75,10 +75,9 @@ fn check_jacobian() { } fn solve_nonlinear_system(genie: Genie) -> Result<(), StrError> { - let one_based = if genie == Genie::Mumps { true } else { false }; let (neq, nnz) = (4, 16); let mut solver = LinSolver::new(genie)?; - let mut jj = SparseMatrix::new_coo(neq, neq, nnz, None, one_based).unwrap(); + let mut jj = SparseMatrix::new_coo(neq, neq, nnz, None).unwrap(); let mut rr = Vector::new(neq); let mut uu = Vector::from(&[0.0, 0.0, 0.0, 0.0]); let mut mdu = Vector::new(neq); diff --git a/russell_sparse/tests/test_umfpack.rs b/russell_sparse/tests/test_umfpack.rs new file mode 100644 index 00000000..76761946 --- /dev/null +++ b/russell_sparse/tests/test_umfpack.rs @@ -0,0 +1,31 @@ +use russell_lab::*; +use russell_sparse::prelude::*; +use russell_sparse::StrError; + +#[test] +fn test_complex_umfpack() -> Result<(), StrError> { + let n = 10; + let d = (n as f64) / 10.0; + let mut coo = CooMatrix::new(n, n, n, None)?; + let mut x_correct = Vector::new(n); + let mut rhs = Vector::new(n); + for k in 0..n { + // put diagonal entries + let akk = 10.0 + (k as f64) * d; + coo.put(k, k, akk)?; + // let the exact solution be k + 0.5i + x_correct[k] = k as f64; + // generate RHS to match solution + rhs[k] = akk * x_correct[k]; + } + // println!("a =\n{}", coo.as_dense()); + // println!("x =\n{}", x_correct); + // println!("b =\n{}", rhs); + let mut x = Vector::new(n); + let mut mat = SparseMatrix::from_coo(coo); + let mut solver = SolverUMFPACK::new()?; + solver.factorize(&mut mat, None)?; + solver.solve(&mut x, &mut mat, &rhs, false)?; + vec_approx_eq(x.as_data(), x_correct.as_data(), 1e-14); + Ok(()) +} diff --git a/russell_stat/src/lib.rs b/russell_stat/src/lib.rs index 55140f85..de49688a 100644 --- a/russell_stat/src/lib.rs +++ b/russell_stat/src/lib.rs @@ -12,7 +12,7 @@ //! //! TODO -/// Defines a type alias for the error type as a static string +/// Defines the error output as a static string pub type StrError = &'static str; mod distribution_frechet; diff --git a/russell_tensor/Cargo.toml b/russell_tensor/Cargo.toml index db717a2e..2db244e1 100644 --- a/russell_tensor/Cargo.toml +++ b/russell_tensor/Cargo.toml @@ -16,4 +16,4 @@ russell_lab = { path = "../russell_lab", version = "0.8.0" } serde = { version = "1.0", features = ["derive"] } [dev-dependencies] -rmp-serde = "1.1" +serde_json = "1.0" diff --git a/russell_tensor/src/lib.rs b/russell_tensor/src/lib.rs index bcf3c6aa..e736f9a7 100644 --- a/russell_tensor/src/lib.rs +++ b/russell_tensor/src/lib.rs @@ -11,7 +11,7 @@ //! //! TODO -/// Defines a type alias for the error type as a static string +/// Defines the error output as a static string pub type StrError = &'static str; mod as_matrix_3x3; diff --git a/russell_tensor/src/tensor2.rs b/russell_tensor/src/tensor2.rs index fd0e8458..8f1e4b49 100644 --- a/russell_tensor/src/tensor2.rs +++ b/russell_tensor/src/tensor2.rs @@ -1748,7 +1748,6 @@ mod tests { use super::Tensor2; use crate::{Mandel, SampleTensor2, SamplesTensor2, IDENTITY2, SQRT_2, SQRT_2_BY_3, SQRT_3, SQRT_3_BY_2}; use russell_lab::{approx_eq, mat_approx_eq, mat_mat_mul, math::PI, vec_approx_eq, Matrix}; - use serde::{Deserialize, Serialize}; #[test] fn new_and_mandel_work() { @@ -2462,15 +2461,12 @@ mod tests { └ ┘" ); // serialize - let mut serialized = Vec::new(); - let mut serializer = rmp_serde::Serializer::new(&mut serialized); - tt.serialize(&mut serializer).unwrap(); - assert!(serialized.len() > 0); + let json = serde_json::to_string(&tt).unwrap(); + assert!(json.len() > 0); // deserialize - let mut deserializer = rmp_serde::Deserializer::new(&serialized[..]); - let ss: Tensor2 = Deserialize::deserialize(&mut deserializer).unwrap(); + let from_json: Tensor2 = serde_json::from_str(&json).unwrap(); assert_eq!( - format!("{:.1}", ss.to_matrix()), + format!("{:.1}", from_json.to_matrix()), "┌ ┐\n\ │ 1.0 2.0 3.0 │\n\ │ 4.0 5.0 6.0 │\n\ diff --git a/russell_tensor/src/tensor4.rs b/russell_tensor/src/tensor4.rs index 42cf91a0..07669152 100644 --- a/russell_tensor/src/tensor4.rs +++ b/russell_tensor/src/tensor4.rs @@ -1113,7 +1113,6 @@ mod tests { use crate::{Mandel, SamplesTensor4}; use crate::{IDENTITY4, P_DEV, P_ISO, P_SKEW, P_SYM, P_SYMDEV, TRACE_PROJECTION, TRANSPOSITION}; use russell_lab::{approx_eq, mat_approx_eq}; - use serde::{Deserialize, Serialize}; #[test] fn new_and_mandel_work() { @@ -1545,15 +1544,12 @@ mod tests { └ ┘" ); // serialize - let mut serialized = Vec::new(); - let mut serializer = rmp_serde::Serializer::new(&mut serialized); - dd.serialize(&mut serializer).unwrap(); - assert!(serialized.len() > 0); + let json = serde_json::to_string(&dd).unwrap(); + assert!(json.len() > 0); // deserialize - let mut deserializer = rmp_serde::Deserializer::new(&serialized[..]); - let ee: Tensor4 = Deserialize::deserialize(&mut deserializer).unwrap(); + let from_json: Tensor4 = serde_json::from_str(&json).unwrap(); assert_eq!( - format!("{:.0}", ee.to_matrix()), + format!("{:.0}", from_json.to_matrix()), "┌ ┐\n\ │ 1111 1122 1133 1112 1123 1113 1112 1123 1113 │\n\ │ 2211 2222 2233 2212 2223 2213 2212 2223 2213 │\n\