diff --git a/Cargo.toml b/Cargo.toml index 89ef53a9..79d52997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "peroxide" -version = "0.37.9" +version = "0.38.0" authors = ["axect "] edition = "2018" description = "Rust comprehensive scientific computation library contains linear algebra, numerical analysis, statistics and machine learning tools with farmiliar syntax" @@ -10,21 +10,29 @@ categories = ["science"] readme = "README.md" documentation = "https://axect.github.io/Peroxide_Doc" keywords = ["Numeric", "Science", "Dataframe", "Plot", "LinearAlgebra"] -exclude = ["example_data/", "src/bin/", "benches/", "example/", "test_data/", "peroxide-ad2"] +exclude = [ + "example_data/", + "src/bin/", + "benches/", + "example/", + "test_data/", + "peroxide-ad2", +] [badges] travis-ci = { repository = "axect/peroxide" } maintenance = { status = "actively-developed" } [dev-dependencies] -float-cmp = "0.9" +float-cmp = "0.10" +criterion = { version = "0.5.1", features = ["html_reports"] } [dependencies] csv = { version = "1.3", optional = true, default-features = false } rand = { version = "0.8", features = ["small_rng"] } rand_distr = "0.4" order-stat = "0.1" -puruspe = "0.2" +puruspe = "0.3" matrixmultiply = { version = "0.3", features = ["threading"] } peroxide-ad = "0.3" peroxide-num = "0.1" @@ -39,10 +47,10 @@ serde = { version = "1.0", features = ["derive"], optional = true } json = { version = "0.12", optional = true } arrow2 = { version = "0.18", features = ["io_parquet", "io_parquet_compression"], optional = true } num-complex = { version = "0.4", optional = true } -lambert_w = { version = "0.3.0", default-features = false, features = ["24bits", "50bits"] } +rayon = { version = "1.10", optional = true } [package.metadata.docs.rs] -rustdoc-args = [ "--html-in-header", "katex-header.html", "--cfg", "docsrs"] +rustdoc-args = ["--html-in-header", "katex-header.html", "--cfg", "docsrs"] [features] default = [] @@ -51,3 +59,4 @@ plot = ["pyo3"] nc = ["netcdf"] parquet = ["arrow2"] complex = ["num-complex", "matrixmultiply/cgemm"] +parallel = ["rayon"] diff --git a/README.md b/README.md index b70e2005..abbc8cc1 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Peroxide provides various features. - `default` - Pure Rust (No dependencies of architecture - Perfect cross compilation) - `O3` - BLAS & LAPACK (Perfect performance but little bit hard to set-up - Strongly recommend to look [Peroxide with BLAS](https://github.com/Axect/Peroxide_BLAS)) - `plot` - With matplotlib of python, we can draw any plots. +- `complex` - With complex numbers (vector, matrix and integral) +- `parallel` - With some parallel functions - `nc` - To handle netcdf file format with DataFrame - `csv` - To handle csv file format with Matrix or DataFrame - `parquet` - To handle parquet file format with DataFrame @@ -311,7 +313,7 @@ How's that? Let me know if there's anything else you'd like me to improve! ## Latest README version -Corresponding to `0.37.7` +Corresponding to `0.38.0` ## Pre-requisite @@ -321,55 +323,38 @@ Corresponding to `0.37.7` ## Install -- Run below commands in your project directory - -1. Default - - ```bash - cargo add peroxide - ``` - -2. OpenBLAS - - ```bash - cargo add peroxide --features O3 - ``` - -3. Plot - - ```bash - cargo add peroxide --features plot - ``` - -4. NetCDF dependency for DataFrame - - ```bash - cargo add peroxide --features nc - ``` - -5. CSV dependency for DataFrame - - ```bash - cargo add peroxide --features csv - ``` +### Basic Installation +```bash +cargo add peroxide +``` -6. Parquet dependency for DataFrame +### Featured Installation +```bash +cargo add peroxide --features "" +``` - ```bash - cargo add peroxide --features parquet - ``` +### Available Features -7. Serialize or Deserialize with Matrix or polynomial +* `O3`: Adds OpenBLAS support +* `plot`: Enables plotting functionality +* `complex`: Supports complex number operations +* `parallel`: Enables parallel processing capabilities +* `nc`: Adds NetCDF support for DataFrame +* `csv`: Adds CSV support for DataFrame +* `parquet`: Adds Parquet support for DataFrame +* `serde`: Enables serialization/deserialization for Matrix and polynomial - ```bash - cargo add peroxide --features serde - ``` +### Install Examples -8. All features +Single feature installation: +```bash +cargo add peroxide --features "plot" +``` - ```bash - cargo add peroxide --features "O3 plot nc csv parquet serde" - ``` +Multiple features installation: +```bash +cargo add peroxide --features "O3 plot nc csv parquet serde" +``` ## Useful tips for features @@ -388,6 +373,11 @@ Corresponding to `0.37.7` - __src__ - [lib.rs](src/lib.rs) : `mod` and `re-export` + - __complex__: For complex vector, matrix & integrals. + - [mod.rs](src/complex/mod.rs) + - [integrate.rs](src/complex/integrate.rs) : Complex integral + - [matrix.rs](src/complex/matrix.rs) : Complex matrix + - [vector.rs](src/complex/vector.rs) : Complex vector - __fuga__ : Fuga for controlling numerical algorithms. - [mod.rs](src/fuga/mod.rs) - __macros__ : Macro files @@ -434,6 +424,7 @@ Corresponding to `0.37.7` - [fp.rs](src/traits/fp.rs) : Functional programming toolbox - [general.rs](src/traits/general.rs) : General algorithms - [math.rs](src/traits/math.rs) : Mathematics + - [matrix.rs](src/traits/matrix.rs) : Matrix traits - [mutable.rs](src/traits/mutable.rs) : Mutable toolbox - [num.rs](src/traits/num.rs) : Number, Real and more operations - [pointer.rs](src/traits/pointer.rs) : Matrix pointer and Vector pointer for convenience diff --git a/RELEASES.md b/RELEASES.md index 430c0af4..d73626db 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,25 @@ +# Release 0.38.0 + +## New features - Complex & Parallel + +- `complex` feature + - Implement complex vector, matrix and integral [#35](https://github.com/Axect/Peroxide/issues/35) (Thanks to [@GComitini](https://github.com/GComitini) and [@soumyasen1809](https://github.com/soumyasen1809)) + +- `parallel` feature + - Implement some parallel functions [#72](https://github.com/Axect/Peroxide/issues/72) (Thanks to [@soumyasen1809](https://github.com/soumyasen1809)) + +## Generic Matrix - MatrixTrait + +- Implement `MatrixTrait` for Matrix (Scalar = f64) +- Implement `MatrixTrait` for ComplexMatrix (Scalar = C64) +- `LinearAlgebra` and `solve` depend on `MatrixTrait` + +## Other changes + +- Update `puruspe` dependency to `0.3.0`, remove `lambert_w` dependency [#79](https://github.com/Axect/Peroxide/pull/79) (Thanks to [@JSorngard](https://github.com/JSorngard)) + +- Add `hermite_polynomial` and `bessel_polynomial` [#80](https://github.com/Axect/Peroxide/pull/80) (Thanks to [@jgrage](https://github.com/jgrage)) + # Release 0.37.9 (2024-07-31) - Fix inconsistent lambert w function name [#65](https://github.com/Axect/Peroxide/issues/65) (Thanks to [@JSorngard](https://github.com/JSorngard)) diff --git a/benches/data/rayon_matrix_benchmark_results.md b/benches/data/rayon_matrix_benchmark_results.md new file mode 100644 index 00000000..f85feae8 --- /dev/null +++ b/benches/data/rayon_matrix_benchmark_results.md @@ -0,0 +1,194 @@ +Lib used for benchmarking: Criterion +Matrix size: 1000x1000 + +Running benches/parallel_rayon/matrix_benchmark.rs + +ser_matrix_bench time: [535.12 µs 544.51 µs 556.68 µs] +Found 11 outliers among 100 measurements (11.00%) + 4 (4.00%) high mild + 7 (7.00%) high severe + +par_matrix_bench time: [5.0912 ms 5.1431 ms 5.1995 ms] +Found 7 outliers among 100 measurements (7.00%) + 1 (1.00%) low mild + 5 (5.00%) high mild + 1 (1.00%) high severe + +ser_py_matrix_bench time: [4.3100 ms 4.3309 ms 4.3544 ms] +Found 7 outliers among 100 measurements (7.00%) + 2 (2.00%) high mild + 5 (5.00%) high severe + +par_py_matrix_bench time: [11.667 ms 11.789 ms 11.920 ms] +Found 10 outliers among 100 measurements (10.00%) + 6 (6.00%) high mild + 4 (4.00%) high severe + +ser_matrix_change_shape_bench + time: [7.3630 ms 7.4075 ms 7.4608 ms] +Found 5 outliers among 100 measurements (5.00%) + 1 (1.00%) high mild + 4 (4.00%) high severe + +par_matrix_change_shape_bench + time: [10.276 ms 10.385 ms 10.499 ms] +Found 3 outliers among 100 measurements (3.00%) + 2 (2.00%) high mild + 1 (1.00%) high severe + +ser_matrix_extract_row_bench + time: [613.39 µs 622.44 µs 633.72 µs] +Found 7 outliers among 100 measurements (7.00%) + 7 (7.00%) high severe + +par_matrix_extract_row_bench + time: [5.4321 ms 5.4851 ms 5.5399 ms] +Found 4 outliers among 100 measurements (4.00%) + 4 (4.00%) high mild + +ser_matrix_from_index_bench + time: [2.4174 ms 2.4490 ms 2.4851 ms] +Found 14 outliers among 100 measurements (14.00%) + 1 (1.00%) high mild + 13 (13.00%) high severe + +par_matrix_from_index_bench + time: [2.3912 ms 2.4090 ms 2.4304 ms] +Found 9 outliers among 100 measurements (9.00%) + 2 (2.00%) high mild + 7 (7.00%) high severe + +ser_matrix_to_vec_bench time: [2.4800 ms 2.5082 ms 2.5423 ms] +Found 10 outliers among 100 measurements (10.00%) + 4 (4.00%) high mild + 6 (6.00%) high severe + +par_matrix_to_vec_bench time: [6.4041 ms 6.4618 ms 6.5250 ms] +Found 6 outliers among 100 measurements (6.00%) + 5 (5.00%) high mild + 1 (1.00%) high severe + +ser_matrix_to_diag_bench + time: [2.4335 ms 2.4526 ms 2.4750 ms] +Found 14 outliers among 100 measurements (14.00%) + 6 (6.00%) high mild + 8 (8.00%) high severe + +par_matrix_to_diag_bench + time: [13.514 ms 13.684 ms 13.868 ms] +Found 10 outliers among 100 measurements (10.00%) + 7 (7.00%) high mild + 3 (3.00%) high severe + +Benchmarking ser_matrix_submat_bench: Warming up for 3.0000 s +Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 8.3s, enable flat sampling, or reduce sample count to 50. +ser_matrix_submat_bench time: [1.6077 ms 1.6243 ms 1.6451 ms] +Found 16 outliers among 100 measurements (16.00%) + 3 (3.00%) high mild + 13 (13.00%) high severe + +par_matrix_submat_bench time: [10.611 ms 10.761 ms 10.942 ms] +Found 5 outliers among 100 measurements (5.00%) + 3 (3.00%) high mild + 2 (2.00%) high severe + +ser_matrix_add_vec_bench + time: [7.3077 ms 7.3485 ms 7.3946 ms] +Found 12 outliers among 100 measurements (12.00%) + 2 (2.00%) high mild + 10 (10.00%) high severe + +par_matrix_add_vec_bench + time: [11.331 ms 11.480 ms 11.636 ms] +Found 2 outliers among 100 measurements (2.00%) + 2 (2.00%) high mild + +ser_matrix_norm_bench time: [5.1600 ms 5.1864 ms 5.2165 ms] +Found 7 outliers among 100 measurements (7.00%) + 1 (1.00%) high mild + 6 (6.00%) high severe + +par_matrix_norm_bench time: [2.6565 ms 2.6810 ms 2.7091 ms] +Found 5 outliers among 100 measurements (5.00%) + 2 (2.00%) high mild + 3 (3.00%) high severe + +Benchmarking ser_matrix_norm_bench #2: Warming up for 3.0000 s +Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 8.9s, enable flat sampling, or reduce sample count to 50. +ser_matrix_norm_bench #2 + time: [1.7262 ms 1.7391 ms 1.7541 ms] +Found 15 outliers among 100 measurements (15.00%) + 10 (10.00%) high mild + 5 (5.00%) high severe + +par_matrix_norm_bench #2 + time: [6.7071 ms 6.7883 ms 6.8703 ms] +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +ser_matrix_norm_bench #3 + time: [9.7582 ms 9.9006 ms 10.057 ms] +Found 12 outliers among 100 measurements (12.00%) + 5 (5.00%) high mild + 7 (7.00%) high severe + +par_matrix_norm_bench #3 + time: [9.3004 ms 9.4088 ms 9.5239 ms] +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +ser_matrix_inner_prod_bench + time: [5.2730 ms 5.3590 ms 5.4583 ms] +Found 14 outliers among 100 measurements (14.00%) + 3 (3.00%) high mild + 11 (11.00%) high severe + +par_matrix_inner_prod_bench + time: [5.0987 ms 5.1644 ms 5.2402 ms] +Found 7 outliers among 100 measurements (7.00%) + 3 (3.00%) high mild + 4 (4.00%) high severe + +ser_matrix_hadamard_bench + time: [5.6521 ms 5.6870 ms 5.7262 ms] +Found 12 outliers among 100 measurements (12.00%) + 3 (3.00%) high mild + 9 (9.00%) high severe + +par_matrix_hadamard_bench + time: [14.155 ms 14.335 ms 14.527 ms] +Found 4 outliers among 100 measurements (4.00%) + 3 (3.00%) high mild + 1 (1.00%) high severe + +ser_matrix_take_row_bench + time: [3.7894 ms 3.8234 ms 3.8613 ms] +Found 15 outliers among 100 measurements (15.00%) + 7 (7.00%) high mild + 8 (8.00%) high severe + +par_matrix_take_row_bench + time: [8.4008 ms 8.5171 ms 8.6523 ms] +Found 9 outliers among 100 measurements (9.00%) + 6 (6.00%) high mild + 3 (3.00%) high severe + +ser_matrix_fpmap_bench time: [3.2526 ms 3.2739 ms 3.2977 ms] +Found 12 outliers among 100 measurements (12.00%) + 2 (2.00%) high mild + 10 (10.00%) high severe + +par_matrix_fpmap_bench time: [10.604 ms 10.765 ms 10.937 ms] +Found 11 outliers among 100 measurements (11.00%) + 8 (8.00%) high mild + 3 (3.00%) high severe + +ser_matrix_reduce_bench time: [2.6748 ms 2.6964 ms 2.7201 ms] +Found 9 outliers among 100 measurements (9.00%) + 6 (6.00%) high mild + 3 (3.00%) high severe + +par_matrix_reduce_bench time: [6.2453 ms 6.3198 ms 6.4034 ms] +Found 6 outliers among 100 measurements (6.00%) + 4 (4.00%) high mild + 2 (2.00%) high severe diff --git a/benches/parallel_rayon/matrix_benchmark.rs b/benches/parallel_rayon/matrix_benchmark.rs new file mode 100644 index 00000000..86b39bc9 --- /dev/null +++ b/benches/parallel_rayon/matrix_benchmark.rs @@ -0,0 +1,103 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use peroxide::{ + fuga::*, + traits::math::{ParallelInnerProduct, ParallelNormed}, +}; + +pub fn par_matrix_from_index_benchmark(cr: &mut Criterion) { + let f = |x: usize, y: usize| 2.0 * (x as f64) * (y as f64); + let size: (usize, usize) = (1000, 1000); + + // Result: 1000x1000 matrix: 2.3662 ms + cr.bench_function("ser_matrix_from_index_bench", |b| { + b.iter(|| black_box(Matrix::from_index(f, size))) + }); + + // Result: 1000x1000 matrix: 2.3355 ms + cr.bench_function("par_matrix_from_index_bench", |b| { + b.iter(|| black_box(Matrix::from_index(f, size))) + }); +} + +// Check: better parallel results (ran test 6 times) +pub fn par_matrix_norm_lpq_benchmark(cr: &mut Criterion) { + let v: Vec = (0..1000000) + .into_iter() + .map(|i: i32| 2.0 * (i as f64)) + .collect::>(); + + // Result: 1000x1000 matrix: [5.5969 ms 5.7555 ms 5.9515 ms 6.0843 ms 6.3072 ms 6.5636 ms] + cr.bench_function("ser_matrix_norm_bench", |b| { + b.iter(|| black_box(matrix(v.clone(), 1000, 1000, Shape::Row).norm(Norm::Lpq(4.0, 2.0)))) + }); + + // Result: 1000x1000 matrix: [3.1796 ms 3.2714 ms 3.3714 ms 3.6123 ms 3.7398 ms 3.8761 ms] + cr.bench_function("par_matrix_norm_bench", |b| { + b.iter(|| { + black_box(matrix(v.clone(), 1000, 1000, Shape::Row).par_norm(Norm::Lpq(4.0, 2.0))) + }) + }); +} + +pub fn par_matrix_norm_l1_benchmark(cr: &mut Criterion) { + let v: Vec = (0..1000000) + .into_iter() + .map(|i: i32| 2.0 * (i as f64)) + .collect::>(); + + // Result: 1000x1000 matrix: 9.0287 ms + cr.bench_function("ser_matrix_norm_bench", |b| { + b.iter(|| black_box(matrix(v.clone(), 1000, 1000, Shape::Row).norm(Norm::L1))) + }); + + // Result: 1000x1000 matrix: 10.393 ms + cr.bench_function("par_matrix_norm_bench", |b| { + b.iter(|| black_box(matrix(v.clone(), 1000, 1000, Shape::Row).par_norm(Norm::L1))) + }); +} + +// Check: better parallel results (ran test 6 times) +pub fn par_matrix_inner_prod_benchmark(cr: &mut Criterion) { + let v: Vec = (0..1000000) + .into_iter() + .map(|i: i32| 2.0 * (i as f64)) + .collect::>(); + + let w: Vec = (0..1000000) + .into_iter() + .map(|i: i32| 3.0 * (i as f64)) + .collect::>(); + + // Result: 1000x1000 matrix: [5.1075 ms 5.1505 ms 5.2013 ms 5.7617 ms 6.0196 ms 6.3009 ms] + cr.bench_function("ser_matrix_inner_prod_bench", |b| { + b.iter(|| { + black_box(matrix(v.clone(), 1000, 1000, Shape::Row).dot(&matrix( + w.clone(), + 1000, + 1000, + Shape::Row, + ))) + }) + }); + + // Result: 1000x1000 matrix: [4.9931 ms 5.0244 ms 5.0642 ms 5.0322 ms 5.0819 ms 5.1404 ms] + cr.bench_function("par_matrix_inner_prod_bench", |b| { + b.iter(|| { + black_box(matrix(v.clone(), 1000, 1000, Shape::Row).par_dot(&matrix( + w.clone(), + 1000, + 1000, + Shape::Row, + ))) + }) + }); +} + +criterion_group!( + benches, + par_matrix_from_index_benchmark, + par_matrix_norm_lpq_benchmark, + par_matrix_norm_l1_benchmark, + par_matrix_inner_prod_benchmark, +); +criterion_main!(benches); diff --git a/examples/b_spline_test.rs b/examples/b_spline_test.rs index 46cdda59..6ae0ae32 100644 --- a/examples/b_spline_test.rs +++ b/examples/b_spline_test.rs @@ -12,7 +12,6 @@ fn main() -> Result<(), Box> { vec![1f64, 2f64], ]; - let spline = BSpline::clamped(degree, knots, control_points.clone())?; let t = linspace(0f64, 3f64, 200); let (x, y): (Vec, Vec) = spline.eval_vec(&t).into_iter().unzip(); @@ -23,8 +22,7 @@ fn main() -> Result<(), Box> { let control_y = control_points.iter().map(|v| v[1]).collect::>(); let mut plt = Plot2D::new(); - plt - .insert_pair((x.clone(), y.clone())) + plt.insert_pair((x.clone(), y.clone())) .insert_pair((control_x.clone(), control_y.clone())) .set_plot_type(vec![(1, PlotType::Scatter)]) .set_color(vec![(0, "darkblue"), (1, "red")]) diff --git a/examples/broyden_test.rs b/examples/broyden_test.rs index 6ddf5653..b3d2c400 100644 --- a/examples/broyden_test.rs +++ b/examples/broyden_test.rs @@ -1,9 +1,13 @@ use peroxide::fuga::*; -use peroxide::numerical::root::{Pt, Intv}; +use peroxide::numerical::root::{Intv, Pt}; fn main() -> Result<(), Box> { let problem = Quadratic; - let broyden = BroydenMethod { max_iter: 100, tol: 1e-6, rtol: 1e-6 }; + let broyden = BroydenMethod { + max_iter: 100, + tol: 1e-6, + rtol: 1e-6, + }; let root = broyden.find(&problem)?; let result = problem.function(root)?; @@ -17,10 +21,7 @@ struct Quadratic; impl RootFindingProblem<2, 2, Intv<2>> for Quadratic { fn function(&self, x: Pt<2>) -> anyhow::Result> { - Ok([ - x[0] * x[0] + x[1] * x[1] - 1.0, - x[0] + x[1] - 2f64.sqrt() - ]) + Ok([x[0] * x[0] + x[1] * x[1] - 1.0, x[0] + x[1] - 2f64.sqrt()]) } fn initial_guess(&self) -> Intv<2> { diff --git a/examples/chebyshev.rs b/examples/chebyshev.rs index 2ccdbe3a..739218ee 100644 --- a/examples/chebyshev.rs +++ b/examples/chebyshev.rs @@ -4,7 +4,7 @@ use peroxide::fuga::*; fn main() { let mut c = chebyshev_polynomial(0, SpecialKind::First); c.print(); - for i in 1 .. 11 { + for i in 1..11 { c = chebyshev_polynomial(i, SpecialKind::First); c.print(); } diff --git a/examples/dataframe.rs b/examples/dataframe.rs index 386184ae..e1374be4 100644 --- a/examples/dataframe.rs +++ b/examples/dataframe.rs @@ -3,7 +3,7 @@ extern crate peroxide; use peroxide::fuga::*; fn main() { - let x = c!(1,2,3,4); + let x = c!(1, 2, 3, 4); let a = Series::new(x); a.print(); println!(""); @@ -19,7 +19,7 @@ fn main() { df.print(); println!(""); - df["1"] = Series::new(c!(5,6,7,8)); + df["1"] = Series::new(c!(5, 6, 7, 8)); df.print(); println!(""); diff --git a/examples/df_csv.rs b/examples/df_csv.rs index eedc28f5..5e051f8c 100644 --- a/examples/df_csv.rs +++ b/examples/df_csv.rs @@ -3,7 +3,7 @@ extern crate peroxide; use peroxide::fuga::*; fn main() -> Result<(), Box> { - let a = Series::new(vec![1,2,3,4]); + let a = Series::new(vec![1, 2, 3, 4]); let b = Series::new(c!(0.1, 0.2, 0.3, 0.4)); let c = Series::new(vec![true, false, true, false]); let d = Series::new(vec!['a', 'b', 'c', 'd']); @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { df.print(); - #[cfg(feature="csv")] + #[cfg(feature = "csv")] { df.write_csv("example_data/df_csv.csv")?; diff --git a/examples/df_nc.rs b/examples/df_nc.rs index f5269276..4859f8eb 100644 --- a/examples/df_nc.rs +++ b/examples/df_nc.rs @@ -2,13 +2,19 @@ extern crate peroxide; use peroxide::fuga::*; fn main() -> Result<(), Box> { - #[cfg(feature = "nc")] { + #[cfg(feature = "nc")] + { let a = Series::new(vec![1, 2, 3, 4]); let b = Series::new(vec![0.1, 0.2, 0.3, 0.4]); let c = Series::new(vec![true, false, false, true]); - let d = Series::new(vec!["a", "b", "c", "d"].into_iter().map(|x| x.to_string()).collect()); + let d = Series::new( + vec!["a", "b", "c", "d"] + .into_iter() + .map(|x| x.to_string()) + .collect(), + ); - let mut df = DataFrame::new(vec![a,b,c,d]); + let mut df = DataFrame::new(vec![a, b, c, d]); df.set_header(vec!["a", "b", "c", "d"]); println!("Write:"); df.print(); diff --git a/examples/gradient.rs b/examples/gradient.rs index d81222c9..585c1f68 100644 --- a/examples/gradient.rs +++ b/examples/gradient.rs @@ -1,12 +1,12 @@ use peroxide::fuga::*; fn main() { - f(2f64).print(); // x^3 = 8 - f_grad(2f64).print(); // 3 * x^2 = 12 - f_hess(2f64).print(); // 6 * x = 12 + f(2f64).print(); // x^3 = 8 + f_grad(2f64).print(); // 3 * x^2 = 12 + f_hess(2f64).print(); // 6 * x = 12 } -#[ad_function] // generates f_grad, f_hess +#[ad_function] // generates f_grad, f_hess fn f(x: f64) -> f64 { - x.powi(3) // x^3 + x.powi(3) // x^3 } diff --git a/examples/lorenz_dp45.rs b/examples/lorenz_dp45.rs index da1de787..ce8c8a7d 100644 --- a/examples/lorenz_dp45.rs +++ b/examples/lorenz_dp45.rs @@ -4,11 +4,7 @@ use peroxide::fuga::*; fn main() -> Result<(), Box> { let dp45 = DP45::new(1e-4, 0.9, 1e-6, 1e-1, 100); let basic_ode_solver = BasicODESolver::new(dp45); - let (_, y_vec) = basic_ode_solver.solve( - &Lorenz, - (0f64, 100f64), - 1e-2, - )?; + let (_, y_vec) = basic_ode_solver.solve(&Lorenz, (0f64, 100f64), 1e-2)?; let y_mat = py_matrix(y_vec); let y0 = y_mat.col(0); let y2 = y_mat.col(2); @@ -16,8 +12,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(y0) + plt.set_domain(y0) .insert_image(y2) .set_xlabel(r"$y_0$") .set_ylabel(r"$y_2$") diff --git a/examples/lorenz_gl4.rs b/examples/lorenz_gl4.rs index ba17bcc4..9a64b57d 100644 --- a/examples/lorenz_gl4.rs +++ b/examples/lorenz_gl4.rs @@ -4,11 +4,7 @@ use peroxide::fuga::*; fn main() -> Result<(), Box> { let gl4 = GL4::new(ImplicitSolver::FixedPoint, 1e-6, 100); let basic_ode_solver = BasicODESolver::new(gl4); - let (_, y_vec) = basic_ode_solver.solve( - &Lorenz, - (0f64, 100f64), - 1e-2, - )?; + let (_, y_vec) = basic_ode_solver.solve(&Lorenz, (0f64, 100f64), 1e-2)?; let y_mat = py_matrix(y_vec); let y0 = y_mat.col(0); let y2 = y_mat.col(2); @@ -16,8 +12,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(y0) + plt.set_domain(y0) .insert_image(y2) .set_xlabel(r"$y_0$") .set_ylabel(r"$y_2$") diff --git a/examples/lorenz_rk4.rs b/examples/lorenz_rk4.rs index 79fc3b9a..8ebc4584 100644 --- a/examples/lorenz_rk4.rs +++ b/examples/lorenz_rk4.rs @@ -3,11 +3,7 @@ use peroxide::fuga::*; #[allow(unused_variables)] fn main() -> Result<(), Box> { let basic_ode_solver = BasicODESolver::new(RK4); - let (_, y_vec) = basic_ode_solver.solve( - &Lorenz, - (0f64, 100f64), - 1e-2, - )?; + let (_, y_vec) = basic_ode_solver.solve(&Lorenz, (0f64, 100f64), 1e-2)?; let y_mat = py_matrix(y_vec); let y0 = y_mat.col(0); let y2 = y_mat.col(2); @@ -15,8 +11,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(y0) + plt.set_domain(y0) .insert_image(y2) .set_xlabel(r"$y_0$") .set_ylabel(r"$y_2$") diff --git a/examples/lorenz_rkf45.rs b/examples/lorenz_rkf45.rs index 83e2bdac..da0453b5 100644 --- a/examples/lorenz_rkf45.rs +++ b/examples/lorenz_rkf45.rs @@ -4,11 +4,7 @@ use peroxide::fuga::*; fn main() -> Result<(), Box> { let rkf45 = RKF45::new(1e-4, 0.9, 1e-6, 1e-2, 100); let basic_ode_solver = BasicODESolver::new(rkf45); - let (_, y_vec) = basic_ode_solver.solve( - &Lorenz, - (0f64, 100f64), - 1e-2, - )?; + let (_, y_vec) = basic_ode_solver.solve(&Lorenz, (0f64, 100f64), 1e-2)?; let y_mat = py_matrix(y_vec); let y0 = y_mat.col(0); let y2 = y_mat.col(2); @@ -16,8 +12,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(y0) + plt.set_domain(y0) .insert_image(y2) .set_xlabel(r"$y_0$") .set_ylabel(r"$y_2$") diff --git a/examples/lorenz_tsit45.rs b/examples/lorenz_tsit45.rs index f923e1c5..5bc147fd 100644 --- a/examples/lorenz_tsit45.rs +++ b/examples/lorenz_tsit45.rs @@ -4,11 +4,7 @@ use peroxide::fuga::*; fn main() -> Result<(), Box> { let tsit45 = TSIT45::new(1e-2, 0.9, 1e-6, 1e-1, 100); let basic_ode_solver = BasicODESolver::new(tsit45); - let (_, y_vec) = basic_ode_solver.solve( - &Lorenz, - (0f64, 100f64), - 1e-2, - )?; + let (_, y_vec) = basic_ode_solver.solve(&Lorenz, (0f64, 100f64), 1e-2)?; let y_mat = py_matrix(y_vec); let y0 = y_mat.col(0); let y2 = y_mat.col(2); @@ -16,8 +12,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(y0) + plt.set_domain(y0) .insert_image(y2) .set_xlabel(r"$y_0$") .set_ylabel(r"$y_2$") diff --git a/examples/matmul.rs b/examples/matmul.rs index 24f0fa51..47716ec6 100644 --- a/examples/matmul.rs +++ b/examples/matmul.rs @@ -7,12 +7,12 @@ fn main() { // Create Matrix let m = rand(row, col); - + // Create another Matrix let n = rand(row, col); - + // Matmul let result = m * n; - result[(row/2, col/2)].print(); + result[(row / 2, col / 2)].print(); } diff --git a/examples/ode_env.rs b/examples/ode_env.rs index e627e8bd..e2836bb7 100644 --- a/examples/ode_env.rs +++ b/examples/ode_env.rs @@ -11,22 +11,15 @@ fn main() -> Result<(), Box> { let c = cubic_hermite_spline(&t, &y, Quadratic)?; - let test_problem = Test { - cs: c, - }; + let test_problem = Test { cs: c }; let basic_ode_solver = BasicODESolver::new(RK4); - let (t_vec, y_vec) = basic_ode_solver.solve( - &test_problem, - (0f64, 10f64), - 0.01, - )?; + let (t_vec, y_vec) = basic_ode_solver.solve(&test_problem, (0f64, 10f64), 0.01)?; let y_vec: Vec = y_vec.into_iter().flatten().collect(); #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(t_vec) + plt.set_domain(t_vec) .insert_image(y_vec) .set_xlabel(r"$t$") .set_ylabel(r"$y$") diff --git a/examples/ode_test_gl4.rs b/examples/ode_test_gl4.rs index 1d1791e5..bf31dee4 100644 --- a/examples/ode_test_gl4.rs +++ b/examples/ode_test_gl4.rs @@ -4,18 +4,13 @@ use peroxide::fuga::*; fn main() -> Result<(), Box> { let gl4 = GL4::new(ImplicitSolver::FixedPoint, 1e-6, 100); let basic_ode_solver = BasicODESolver::new(gl4); - let (t_vec, y_vec) = basic_ode_solver.solve( - &Test, - (0f64, 10f64), - 0.01, - )?; + let (t_vec, y_vec) = basic_ode_solver.solve(&Test, (0f64, 10f64), 0.01)?; let y_vec: Vec = y_vec.into_iter().flatten().collect(); #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(t_vec) + plt.set_domain(t_vec) .insert_image(y_vec) .set_xlabel(r"$t$") .set_ylabel(r"$y$") diff --git a/examples/ode_test_rk4.rs b/examples/ode_test_rk4.rs index a531aa3d..6b90f5c3 100644 --- a/examples/ode_test_rk4.rs +++ b/examples/ode_test_rk4.rs @@ -3,18 +3,13 @@ use peroxide::fuga::*; #[allow(unused_variables)] fn main() -> Result<(), Box> { let basic_ode_solver = BasicODESolver::new(RK4); - let (t_vec, y_vec) = basic_ode_solver.solve( - &Test, - (0f64, 10f64), - 1e-3, - )?; + let (t_vec, y_vec) = basic_ode_solver.solve(&Test, (0f64, 10f64), 1e-3)?; let y_vec: Vec = y_vec.into_iter().flatten().collect(); #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(t_vec) + plt.set_domain(t_vec) .insert_image(y_vec) .set_xlabel(r"$t$") .set_ylabel(r"$y$") diff --git a/examples/ode_test_rkf45.rs b/examples/ode_test_rkf45.rs index 7441c3a1..04ea96c6 100644 --- a/examples/ode_test_rkf45.rs +++ b/examples/ode_test_rkf45.rs @@ -4,18 +4,13 @@ use peroxide::fuga::*; fn main() -> Result<(), Box> { let rkf = RKF45::new(1e-4, 0.9, 1e-6, 1e-1, 100); let basic_ode_solver = BasicODESolver::new(rkf); - let (t_vec, y_vec) = basic_ode_solver.solve( - &Test, - (0f64, 10f64), - 0.01, - )?; + let (t_vec, y_vec) = basic_ode_solver.solve(&Test, (0f64, 10f64), 0.01)?; let y_vec: Vec = y_vec.into_iter().flatten().collect(); #[cfg(feature = "plot")] { let mut plt = Plot2D::new(); - plt - .set_domain(t_vec) + plt.set_domain(t_vec) .insert_image(y_vec) .set_xlabel(r"$t$") .set_ylabel(r"$y$") diff --git a/examples/optim.rs b/examples/optim.rs index 066578d2..97389eaa 100644 --- a/examples/optim.rs +++ b/examples/optim.rs @@ -21,20 +21,17 @@ fn main() { // Optimizer setting let mut opt = Optimizer::new(data, quad); - let p = opt.set_init_param(n_init) + let p = opt + .set_init_param(n_init) .set_max_iter(50) .set_method(LevenbergMarquardt) .optimize(); - p.print(); // Optimized parameter - opt.get_error().print(); // Optimized RMSE + p.print(); // Optimized parameter + opt.get_error().print(); // Optimized RMSE } fn quad(x: &Vec, n: Vec) -> Option> { - Some( - x.clone().into_iter() - .map(|t| pow_temp(t, n[0])) - .collect() - ) + Some(x.clone().into_iter().map(|t| pow_temp(t, n[0])).collect()) } #[inline] diff --git a/examples/pseudo_inv.rs b/examples/pseudo_inv.rs index bf7436c8..4c29615a 100644 --- a/examples/pseudo_inv.rs +++ b/examples/pseudo_inv.rs @@ -3,7 +3,7 @@ use peroxide::fuga::*; fn main() { let a = ml_matrix("2 -1 0;4 3 -2"); - #[cfg(feature="O3")] + #[cfg(feature = "O3")] { let b = a.pseudo_inv(); assert_eq!(&a * &b, eye(2)); diff --git a/examples/root_macro_test.rs b/examples/root_macro_test.rs index 31466596..5fe4c18a 100644 --- a/examples/root_macro_test.rs +++ b/examples/root_macro_test.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate peroxide; -use peroxide::fuga::*; use anyhow::Result; +use peroxide::fuga::*; fn main() -> Result<()> { let root_bisect = bisection!(f, (0.0, 2.0), 100, 1e-6)?; diff --git a/examples/root_test.rs b/examples/root_test.rs index 06bca33e..13b223ab 100644 --- a/examples/root_test.rs +++ b/examples/root_test.rs @@ -1,12 +1,24 @@ -use peroxide::fuga::*; use anyhow::Result; +use peroxide::fuga::*; fn main() -> Result<()> { let problem = Cubic; - let bisect = BisectionMethod { max_iter: 100, tol: 1e-6 }; - let newton = NewtonMethod { max_iter: 100, tol: 1e-6 }; - let false_pos = FalsePositionMethod { max_iter: 100, tol: 1e-6 }; - let secant = SecantMethod { max_iter: 100, tol: 1e-6 }; + let bisect = BisectionMethod { + max_iter: 100, + tol: 1e-6, + }; + let newton = NewtonMethod { + max_iter: 100, + tol: 1e-6, + }; + let false_pos = FalsePositionMethod { + max_iter: 100, + tol: 1e-6, + }; + let secant = SecantMethod { + max_iter: 100, + tol: 1e-6, + }; let result_bisect = bisect.find(&problem)?; let result_newton = newton.find(&problem)?; let result_false_pos = false_pos.find(&problem)?; diff --git a/examples/svd.rs b/examples/svd.rs index ac29be96..ab98b644 100644 --- a/examples/svd.rs +++ b/examples/svd.rs @@ -3,7 +3,7 @@ use peroxide::fuga::*; fn main() { let a = ml_matrix("14 2;4 22;16 13") / 15f64; - #[cfg(feature="O3")] + #[cfg(feature = "O3")] { let svd = a.svd(); svd.u.print(); diff --git a/src/complex/integral.rs b/src/complex/integral.rs new file mode 100644 index 00000000..63fb2427 --- /dev/null +++ b/src/complex/integral.rs @@ -0,0 +1,53 @@ +use crate::complex::C64; +use crate::numerical::integral::{GKIntegrable, GLKIntegrable, NCIntegrable}; +use crate::structure::polynomial::{lagrange_polynomial, Calculus, Polynomial}; + +// Newton Cotes Quadrature for Complex Functions of one Real Variable +impl NCIntegrable for C64 { + type NodeY = (Vec, Vec); + type NCPolynomial = (Polynomial, Polynomial); + + fn compute_node_y(f: F, node_x: &[f64]) -> Self::NodeY + where + F: Fn(f64) -> Self, + { + node_x + .iter() + .map(|x| { + let z = f(*x); + (z.re, z.im) + }) + .unzip() + } + + fn compute_polynomial(node_x: &[f64], node_y: &Self::NodeY) -> Self::NCPolynomial { + ( + lagrange_polynomial(node_x.to_vec(), node_y.0.to_vec()), + lagrange_polynomial(node_x.to_vec(), node_y.1.to_vec()), + ) + } + + fn integrate_polynomial(p: &Self::NCPolynomial) -> Self::NCPolynomial { + (p.0.integral(), p.1.integral()) + } + + fn evaluate_polynomial(p: &Self::NCPolynomial, x: f64) -> Self { + p.0.eval(x) + C64::I * p.1.eval(x) + } +} + +// Gauss Lagrange and Kronrod Quadrature for Complex Functions of one Real Variable +impl GLKIntegrable for C64 { + const ZERO: Self = C64::ZERO; +} + +// Gauss Kronrod Quadrature for Complex Functions of one Real Variable +impl GKIntegrable for C64 { + fn is_finite(&self) -> bool { + C64::is_finite(*self) + } + + fn gk_norm(&self) -> f64 { + self.norm() + } +} diff --git a/src/complex/matrix.rs b/src/complex/matrix.rs new file mode 100644 index 00000000..cc489e88 --- /dev/null +++ b/src/complex/matrix.rs @@ -0,0 +1,2711 @@ +use std::{ + cmp::{max, min}, + fmt, + ops::{Add, Div, Index, IndexMut, Mul, Neg, Sub}, +}; + +use anyhow::{bail, Result}; +use matrixmultiply::CGemmOption; +use num_complex::Complex; +use peroxide_num::{ExpLogOps, PowOps, TrigOps}; +use rand_distr::num_traits::{One, Zero}; + +use crate::{ + complex::C64, + structure::matrix::Shape, + traits::fp::{FPMatrix, FPVector}, + traits::general::Algorithm, + traits::math::{InnerProduct, LinearOp, MatrixProduct, Norm, Normed, Vector}, + traits::matrix::{Form, LinearAlgebra, MatrixTrait, SolveKind, PQLU, QR, SVD, WAZD}, + traits::mutable::MutMatrix, + util::low_level::{copy_vec_ptr, swap_vec_ptr}, + util::non_macro::ConcatenateError, + util::useful::{nearly_eq, tab}, +}; + +/// R-like complex matrix structure +/// +/// # Examples +/// +/// ```rust +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::ComplexMatrix; +/// +/// let v1 = ComplexMatrix { +/// data: vec![ +/// C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64), +/// ], +/// row: 2, +/// col: 2, +/// shape: Row, +/// }; // [[1+1i,2+2i],[3+3i,4+4i]] +/// ``` +#[derive(Debug, Clone, Default)] +pub struct ComplexMatrix { + pub data: Vec, + pub row: usize, + pub col: usize, + pub shape: Shape, +} + +// ============================================================================= +// Various complex matrix constructor +// ============================================================================= + +/// R-like complex matrix constructor +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::cmatrix; +/// +/// fn main() { +/// let a = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row +/// ); +/// a.col.print(); // Print matrix column +/// } +/// ``` +pub fn cmatrix(v: Vec, r: usize, c: usize, shape: Shape) -> ComplexMatrix +where + T: Into, +{ + ComplexMatrix { + data: v.into_iter().map(|t| t.into()).collect::>(), + row: r, + col: c, + shape, + } +} + +/// R-like complex matrix constructor (Explicit ver.) +pub fn r_cmatrix(v: Vec, r: usize, c: usize, shape: Shape) -> ComplexMatrix +where + T: Into, +{ + cmatrix(v, r, c, shape) +} + +/// Python-like complex matrix constructor +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let a = py_cmatrix(vec![vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64)], +/// vec![C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)] +/// ]); +/// let b = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row +/// ); +/// assert_eq!(a, b); +/// } +/// ``` +pub fn py_cmatrix(v: Vec>) -> ComplexMatrix +where + T: Into + Copy, +{ + let r = v.len(); + let c = v[0].len(); + let data: Vec = v.into_iter().flatten().collect(); + cmatrix(data, r, c, Shape::Row) +} + +/// Matlab-like matrix constructor +/// +/// Note that the entries to the `ml_cmatrix` +/// needs to be in the `a+bi` format +/// without any spaces between the real and imaginary +/// parts of the Complex number. +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let a = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 3.0+3.0i 4.0+4.0i"); +/// let b = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row +/// ); +/// assert_eq!(a, b); +/// } +/// ``` +pub fn ml_cmatrix(s: &str) -> ComplexMatrix { + let str_row = s.split(";").collect::>(); + let r = str_row.len(); + let str_data = str_row + .iter() + .map(|x| x.trim().split(" ").collect::>()) + .collect::>>(); + let c = str_data[0].len(); + let data = str_data + .iter() + .flat_map(|t| { + t.iter() + .map(|x| x.parse::().unwrap()) + .collect::>() + }) + .collect::>(); + + cmatrix(data, r, c, Shape::Row) +} + +/// Pretty Print +impl fmt::Display for ComplexMatrix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.spread()) + } +} + +/// PartialEq implements +impl PartialEq for ComplexMatrix { + fn eq(&self, other: &ComplexMatrix) -> bool { + if self.shape == other.shape { + self.data + .clone() + .into_iter() + .zip(other.data.clone()) + .all(|(x, y)| nearly_eq(x.re, y.re) && nearly_eq(x.im, y.im)) + && self.row == other.row + } else { + self.eq(&other.change_shape()) + } + } +} + +impl MatrixTrait for ComplexMatrix { + type Scalar = C64; + + /// Raw pointer for `self.data` + fn ptr(&self) -> *const C64 { + &self.data[0] as *const C64 + } + + /// Raw mutable pointer for `self.data` + fn mut_ptr(&mut self) -> *mut C64 { + &mut self.data[0] as *mut C64 + } + + /// Slice of `self.data` + /// + /// # Examples + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// let b = a.as_slice(); + /// assert_eq!(b, &[C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)]); + /// ``` + fn as_slice(&self) -> &[C64] { + &self.data[..] + } + + /// Mutable slice of `self.data` + /// + /// # Examples + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// let mut a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// let mut b = a.as_mut_slice(); + /// b[1] = C64::new(5f64, 5f64); + /// assert_eq!(b, &[C64::new(1f64, 1f64), + /// C64::new(5f64, 5f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)]); + /// assert_eq!(a, cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(5f64, 5f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row)); + /// ``` + fn as_mut_slice(&mut self) -> &mut [C64] { + &mut self.data[..] + } + + /// Change Bindings + /// + /// `Row` -> `Col` or `Col` -> `Row` + /// + /// # Examples + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// let mut a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// assert_eq!(a.shape, Row); + /// let b = a.change_shape(); + /// assert_eq!(b.shape, Col); + /// ``` + fn change_shape(&self) -> Self { + let r = self.row; + let c = self.col; + assert_eq!(r * c, self.data.len()); + let l = r * c - 1; + let mut data: Vec = self.data.clone(); + let ref_data = &self.data; + + match self.shape { + Shape::Row => { + for i in 0..l { + let s = (i * c) % l; + data[i] = ref_data[s]; + } + data[l] = ref_data[l]; + cmatrix(data, r, c, Shape::Col) + } + Shape::Col => { + for i in 0..l { + let s = (i * r) % l; + data[i] = ref_data[s]; + } + data[l] = ref_data[l]; + cmatrix(data, r, c, Shape::Row) + } + } + } + + /// Change Bindings Mutably + /// + /// `Row` -> `Col` or `Col` -> `Row` + /// + /// # Examples + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// let mut a = cmatrix(vec![ + /// C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64) + /// ], + /// 2, 2, Row + /// ); + /// assert_eq!(a.shape, Row); + /// a.change_shape_mut(); + /// assert_eq!(a.shape, Col); + /// ``` + fn change_shape_mut(&mut self) { + let r = self.row; + let c = self.col; + assert_eq!(r * c, self.data.len()); + let l = r * c - 1; + let ref_data = self.data.clone(); + + match self.shape { + Shape::Row => { + for i in 0..l { + let s = (i * c) % l; + self.data[i] = ref_data[s]; + } + self.data[l] = ref_data[l]; + self.shape = Shape::Col; + } + Shape::Col => { + for i in 0..l { + let s = (i * r) % l; + self.data[i] = ref_data[s]; + } + self.data[l] = ref_data[l]; + self.shape = Shape::Row; + } + } + } + + /// Spread data(1D vector) to 2D formatted String + /// + /// # Examples + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// println!("{}", a.spread()); // same as println!("{}", a); + /// // Result: + /// // c[0] c[1] + /// // r[0] 1+1i 3+3i + /// // r[1] 2+2i 4+4i + /// ``` + fn spread(&self) -> String { + assert_eq!(self.row * self.col, self.data.len()); + let r = self.row; + let c = self.col; + let mut key_row = 20usize; + let mut key_col = 20usize; + + if r > 100 || c > 100 || (r > 20 && c > 20) { + let part = if r <= 10 { + key_row = r; + key_col = 100; + self.take_col(100) + } else if c <= 10 { + key_row = 100; + key_col = c; + self.take_row(100) + } else { + self.take_row(20).take_col(20) + }; + return format!( + "Result is too large to print - {}x{}\n only print {}x{} parts:\n{}", + self.row.to_string(), + self.col.to_string(), + key_row.to_string(), + key_col.to_string(), + part.spread() + ); + } + + // Find maximum length of data + let sample = self.data.clone(); + let mut space: usize = sample + .into_iter() + .map( + |x| min(format!("{:.4}", x).len(), x.to_string().len()), // Choose minimum of approx vs normal + ) + .fold(0, |x, y| max(x, y)) + + 1; + + if space < 5 { + space = 5; + } + + let mut result = String::new(); + + result.push_str(&tab("", 5)); + for i in 0..c { + result.push_str(&tab(&format!("c[{}]", i), space)); // Header + } + result.push('\n'); + + for i in 0..r { + result.push_str(&tab(&format!("r[{}]", i), 5)); + for j in 0..c { + let st1 = format!("{:.4}", self[(i, j)]); // Round at fourth position + let st2 = self[(i, j)].to_string(); // Normal string + let mut st = st2.clone(); + + // Select more small thing + if st1.len() < st2.len() { + st = st1; + } + + result.push_str(&tab(&st, space)); + } + if i == (r - 1) { + break; + } + result.push('\n'); + } + + return result; + } + + /// Extract Column + /// + /// # Examples + /// ```rust + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// assert_eq!(a.col(0), vec![C64::new(1f64, 1f64), C64::new(3f64, 3f64)]); + /// } + /// ``` + fn col(&self, index: usize) -> Vec { + assert!(index < self.col); + let mut container: Vec = vec![Complex::zero(); self.row]; + for i in 0..self.row { + container[i] = self[(i, index)]; + } + container + } + + /// Extract Row + /// + /// # Examples + /// ```rust + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// assert_eq!(a.row(0), vec![C64::new(1f64, 1f64), C64::new(2f64, 2f64)]); + /// } + /// ``` + fn row(&self, index: usize) -> Vec { + assert!(index < self.row); + let mut container: Vec = vec![Complex::zero(); self.col]; + for i in 0..self.col { + container[i] = self[(index, i)]; + } + container + } + + /// Extract diagonal components + /// + /// # Examples + /// ```rust + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// assert_eq!(a.diag(), vec![C64::new(1f64, 1f64) ,C64::new(4f64, 4f64)]); + /// } + /// ``` + fn diag(&self) -> Vec { + let mut container = vec![Complex::zero(); self.row]; + let r = self.row; + let c = self.col; + assert_eq!(r, c); + + let c2 = c + 1; + for i in 0..r { + container[i] = self.data[i * c2]; + } + container + } + + /// Transpose + /// + /// # Examples + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// let a_t = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Col + /// ); + /// + /// assert_eq!(a.transpose(), a_t); + /// ``` + fn transpose(&self) -> Self { + match self.shape { + Shape::Row => cmatrix(self.data.clone(), self.col, self.row, Shape::Col), + Shape::Col => cmatrix(self.data.clone(), self.col, self.row, Shape::Row), + } + } + + /// Substitute Col + #[inline] + fn subs_col(&mut self, idx: usize, v: &[C64]) { + for i in 0..self.row { + self[(i, idx)] = v[i]; + } + } + + /// Substitute Row + #[inline] + fn subs_row(&mut self, idx: usize, v: &[C64]) { + for j in 0..self.col { + self[(idx, j)] = v[j]; + } + } + + /// From index operations + fn from_index(f: F, size: (usize, usize)) -> ComplexMatrix + where + F: Fn(usize, usize) -> G + Copy, + G: Into, + { + let row = size.0; + let col = size.1; + + let mut mat = cmatrix(vec![Complex::zero(); row * col], row, col, Shape::Row); + + for i in 0..row { + for j in 0..col { + mat[(i, j)] = f(i, j).into(); + } + } + mat + } + + /// Matrix to `Vec>` + /// + /// To send `Matrix` to `inline-python` + fn to_vec(&self) -> Vec> { + let mut result = vec![vec![Complex::zero(); self.col]; self.row]; + for i in 0..self.row { + result[i] = self.row(i); + } + result + } + + fn to_diag(&self) -> ComplexMatrix { + assert_eq!(self.row, self.col, "Should be square matrix"); + let mut result = cmatrix( + vec![Complex::zero(); self.row * self.col], + self.row, + self.col, + Shape::Row, + ); + let diag = self.diag(); + for i in 0..self.row { + result[(i, i)] = diag[i]; + } + result + } + + /// Submatrix + /// + /// # Description + /// Return below elements of complex matrix to a new complex matrix + /// + /// $$ + /// \begin{pmatrix} + /// \\ddots & & & & \\\\ + /// & start & \\cdots & end.1 & \\\\ + /// & \\vdots & \\ddots & \\vdots & \\\\ + /// & end.0 & \\cdots & end & \\\\ + /// & & & & \\ddots + /// \end{pmatrix} + /// $$ + /// + /// # Examples + /// ```rust + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = ml_cmatrix("1.0+1.0i 2.0+2.0i 3.0+3.0i; + /// 4.0+4.0i 5.0+5.0i 6.0+6.0i; + /// 7.0+7.0i 8.0+8.0i 9.0+9.0i"); + /// let b = cmatrix(vec![C64::new(5f64, 5f64), + /// C64::new(6f64, 6f64), + /// C64::new(8f64, 8f64), + /// C64::new(9f64, 9f64)], + /// 2, 2, Row + /// ); + /// let c = a.submat((1, 1), (2, 2)); + /// assert_eq!(b, c); + /// } + /// ``` + fn submat(&self, start: (usize, usize), end: (usize, usize)) -> ComplexMatrix { + let row = end.0 - start.0 + 1; + let col = end.1 - start.1 + 1; + let mut result = cmatrix(vec![Complex::zero(); row * col], row, col, self.shape); + for i in 0..row { + for j in 0..col { + result[(i, j)] = self[(start.0 + i, start.1 + j)]; + } + } + result + } + + /// Substitute complex matrix to specific position + /// + /// # Description + /// Substitute below elements of complex matrix + /// + /// $$ + /// \begin{pmatrix} + /// \\ddots & & & & \\\\ + /// & start & \\cdots & end.1 & \\\\ + /// & \\vdots & \\ddots & \\vdots & \\\\ + /// & end.0 & \\cdots & end & \\\\ + /// & & & & \\ddots + /// \end{pmatrix} + /// $$ + /// + /// # Examples + /// ``` + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let mut a = ml_cmatrix("1.0+1.0i 2.0+2.0i 3.0+3.0i; + /// 4.0+4.0i 5.0+5.0i 6.0+6.0i; + /// 7.0+7.0i 8.0+8.0i 9.0+9.0i"); + /// let b = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row); + /// let c = ml_cmatrix("1.0+1.0i 2.0+2.0i 3.0+3.0i; + /// 4.0+4.0i 1.0+1.0i 2.0+2.0i; + /// 7.0+7.0i 3.0+3.0i 4.0+4.0i"); + /// a.subs_mat((1,1), (2,2), &b); + /// assert_eq!(a, c); + /// } + /// ``` + fn subs_mat(&mut self, start: (usize, usize), end: (usize, usize), m: &ComplexMatrix) { + let row = end.0 - start.0 + 1; + let col = end.1 - start.1 + 1; + for i in 0..row { + for j in 0..col { + self[(start.0 + i, start.1 + j)] = m[(i, j)]; + } + } + } +} + +// ============================================================================= +// Mathematics for Matrix +// ============================================================================= +impl Vector for ComplexMatrix { + type Scalar = C64; + + fn add_vec(&self, other: &Self) -> Self { + assert_eq!(self.row, other.row); + assert_eq!(self.col, other.col); + + let mut result = cmatrix(self.data.clone(), self.row, self.col, self.shape); + for i in 0..self.row { + for j in 0..self.col { + result[(i, j)] += other[(i, j)]; + } + } + result + } + + fn sub_vec(&self, other: &Self) -> Self { + assert_eq!(self.row, other.row); + assert_eq!(self.col, other.col); + + let mut result = cmatrix(self.data.clone(), self.row, self.col, self.shape); + for i in 0..self.row { + for j in 0..self.col { + result[(i, j)] -= other[(i, j)]; + } + } + result + } + + fn mul_scalar(&self, other: Self::Scalar) -> Self { + let scalar = other; + self.fmap(|x| x * scalar) + } +} + +impl Normed for ComplexMatrix { + type UnsignedScalar = f64; + + fn norm(&self, kind: Norm) -> Self::UnsignedScalar { + match kind { + Norm::F => { + let mut s = Complex::zero(); + for i in 0..self.data.len() { + s += self.data[i].powi(2); + } + s.sqrt().re + } + Norm::Lpq(p, q) => { + let mut s = Complex::zero(); + for j in 0..self.col { + let mut s_row = Complex::zero(); + for i in 0..self.row { + s_row += self[(i, j)].powi(p as i32); + } + s += s_row.powf(q / p); + } + s.powf(1f64 / q).re + } + Norm::L1 => { + let mut m = Complex::zero(); + match self.shape { + Shape::Row => self.change_shape().norm(Norm::L1), + Shape::Col => { + for c in 0..self.col { + let s: C64 = self.col(c).iter().sum(); + if s.re > m.re { + m = s; + } + } + m.re + } + } + } + Norm::LInf => { + let mut m = Complex::zero(); + match self.shape { + Shape::Col => self.change_shape().norm(Norm::LInf), + Shape::Row => { + for r in 0..self.row { + let s: C64 = self.row(r).iter().sum(); + if s.re > m.re { + m = s; + } + } + m.re + } + } + } + Norm::L2 => { + unimplemented!() + } + Norm::Lp(_) => unimplemented!(), + } + } + + fn normalize(&self, _kind: Norm) -> Self + where + Self: Sized, + { + unimplemented!() + } +} + +/// Frobenius inner product +impl InnerProduct for ComplexMatrix { + fn dot(&self, rhs: &Self) -> C64 { + if self.shape == rhs.shape { + self.data.dot(&rhs.data) + } else { + self.data.dot(&rhs.change_shape().data) + } + } +} + +/// TODO: Transpose + +/// Matrix as Linear operator for Vector +#[allow(non_snake_case)] +impl LinearOp, Vec> for ComplexMatrix { + fn apply(&self, other: &Vec) -> Vec { + assert_eq!(self.col, other.len()); + let mut c = vec![Complex::zero(); self.row]; + cgemv(Complex::one(), self, other, Complex::zero(), &mut c); + c + } +} + +/// R like cbind - concatenate two comlex matrix by column direction +pub fn complex_cbind(m1: ComplexMatrix, m2: ComplexMatrix) -> Result { + let mut temp = m1; + if temp.shape != Shape::Col { + temp = temp.change_shape(); + } + + let mut temp2 = m2; + if temp2.shape != Shape::Col { + temp2 = temp2.change_shape(); + } + + let mut v = temp.data; + let mut c = temp.col; + let r = temp.row; + + if r != temp2.row { + bail!(ConcatenateError::DifferentLength); + } + v.extend_from_slice(&temp2.data[..]); + c += temp2.col; + Ok(cmatrix(v, r, c, Shape::Col)) +} + +/// R like rbind - concatenate two complex matrix by row direction +/// ``` +pub fn complex_rbind(m1: ComplexMatrix, m2: ComplexMatrix) -> Result { + let mut temp = m1; + if temp.shape != Shape::Row { + temp = temp.change_shape(); + } + + let mut temp2 = m2; + if temp2.shape != Shape::Row { + temp2 = temp2.change_shape(); + } + + let mut v = temp.data; + let c = temp.col; + let mut r = temp.row; + + if c != temp2.col { + bail!(ConcatenateError::DifferentLength); + } + v.extend_from_slice(&temp2.data[..]); + r += temp2.row; + Ok(cmatrix(v, r, c, Shape::Row)) +} + +impl MatrixProduct for ComplexMatrix { + fn kronecker(&self, other: &Self) -> Self { + let r1 = self.row; + let c1 = self.col; + + let mut result = self[(0, 0)] * other; + + for j in 1..c1 { + let n = self[(0, j)] * other; + result = complex_cbind(result, n).unwrap(); + } + + for i in 1..r1 { + let mut m = self[(i, 0)] * other; + for j in 1..c1 { + let n = self[(i, j)] * other; + m = complex_cbind(m, n).unwrap(); + } + result = complex_rbind(result, m).unwrap(); + } + result + } + + fn hadamard(&self, other: &Self) -> Self { + assert_eq!(self.row, other.row); + assert_eq!(self.col, other.col); + + let r = self.row; + let c = self.col; + + let mut m = cmatrix(vec![Complex::zero(); r * c], r, c, self.shape); + for i in 0..r { + for j in 0..c { + m[(i, j)] = self[(i, j)] * other[(i, j)] + } + } + m + } +} + +// ============================================================================= +// Common Properties of Matrix & Vec +// ============================================================================= +/// `Complex Matrix` to `Vec` +impl Into> for ComplexMatrix { + fn into(self) -> Vec { + self.data + } +} + +/// `&ComplexMatrix` to `&Vec` +impl<'a> Into<&'a Vec> for &'a ComplexMatrix { + fn into(self) -> &'a Vec { + &self.data + } +} + +/// `Vec` to `ComplexMatrix` +impl Into for Vec { + fn into(self) -> ComplexMatrix { + let l = self.len(); + cmatrix(self, l, 1, Shape::Col) + } +} + +/// `&Vec` to `ComplexMatrix` +impl Into for &Vec { + fn into(self) -> ComplexMatrix { + let l = self.len(); + cmatrix(self.clone(), l, 1, Shape::Col) + } +} + +// ============================================================================= +// Standard Operation for Complex Matrix (ADD) +// ============================================================================= + +/// Element-wise addition of Complex Matrix +/// +/// # Caution +/// > You should remember ownership. +/// > If you use ComplexMatrix `a,b` then you can't use them after. +impl Add for ComplexMatrix { + type Output = Self; + + fn add(self, other: Self) -> Self { + assert_eq!(&self.row, &other.row); + assert_eq!(&self.col, &other.col); + + let mut result = cmatrix(self.data.clone(), self.row, self.col, self.shape); + for i in 0..self.row { + for j in 0..self.col { + result[(i, j)] += other[(i, j)]; + } + } + result + } +} + +impl<'a, 'b> Add<&'b ComplexMatrix> for &'a ComplexMatrix { + type Output = ComplexMatrix; + + fn add(self, rhs: &'b ComplexMatrix) -> Self::Output { + self.add_vec(rhs) + } +} + +/// Element-wise addition between Complex Matrix & C64 +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let mut a = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 4.0+4.0i 5.0+5.0i"); +/// let a_exp = ml_cmatrix("2.0+2.0i 3.0+3.0i; +/// 5.0+5.0i 6.0+6.0i"); +/// assert_eq!(a + C64::new(1_f64, 1_f64), a_exp); +/// } +/// ``` +impl Add for ComplexMatrix +where + T: Into + Copy, +{ + type Output = Self; + fn add(self, other: T) -> Self { + self.fmap(|x| x + other.into()) + } +} + +/// Element-wise addition between &ComplexMatrix & C64 +impl<'a, T> Add for &'a ComplexMatrix +where + T: Into + Copy, +{ + type Output = ComplexMatrix; + + fn add(self, other: T) -> Self::Output { + self.fmap(|x| x + other.into()) + } +} + +// Element-wise addition between C64 & ComplexMatrix +/// +/// # Examples +/// +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let mut a = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 4.0+4.0i 5.0+5.0i"); +/// let a_exp = ml_cmatrix("2.0+2.0i 3.0+3.0i; +/// 5.0+5.0i 6.0+6.0i"); +/// assert_eq!(C64::new(1_f64, 1_f64) + a, a_exp); +/// } +/// ``` +impl Add for C64 { + type Output = ComplexMatrix; + + fn add(self, other: ComplexMatrix) -> Self::Output { + other.add(self) + } +} + +/// Element-wise addition between C64 & &ComplexMatrix +impl<'a> Add<&'a ComplexMatrix> for C64 { + type Output = ComplexMatrix; + + fn add(self, other: &'a ComplexMatrix) -> Self::Output { + other.add(self) + } +} + +// ============================================================================= +// Standard Operation for Matrix (Neg) +// ============================================================================= +/// Negation of Complex Matrix +/// +/// # Examples +/// ```rust +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// let a = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row); +/// let a_neg = cmatrix(vec![C64::new(-1f64, -1f64), +/// C64::new(-2f64, -2f64), +/// C64::new(-3f64, -3f64), +/// C64::new(-4f64, -4f64)], +/// 2, 2, Row); +/// assert_eq!(-a, a_neg); +/// ``` +impl Neg for ComplexMatrix { + type Output = Self; + + fn neg(self) -> Self { + cmatrix( + self.data.into_iter().map(|x: C64| -x).collect::>(), + self.row, + self.col, + self.shape, + ) + } +} + +/// Negation of &'a Complex Matrix +impl<'a> Neg for &'a ComplexMatrix { + type Output = ComplexMatrix; + + fn neg(self) -> Self::Output { + cmatrix( + self.data + .clone() + .into_iter() + .map(|x: C64| -x) + .collect::>(), + self.row, + self.col, + self.shape, + ) + } +} + +// ============================================================================= +// Standard Operation for Matrix (Sub) +// ============================================================================= +/// Subtraction between Complex Matrix +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let a = ml_cmatrix("10.0+10.0i 20.0+20.0i; +/// 40.0+40.0i 50.0+50.0i"); +/// let b = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 4.0+4.0i 5.0+5.0i"); +/// let diff = ml_cmatrix("9.0+9.0i 18.0+18.0i; +/// 36.0+36.0i 45.0+45.0i"); +/// assert_eq!(a-b, diff); +/// } +/// ``` +impl Sub for ComplexMatrix { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + assert_eq!(&self.row, &other.row); + assert_eq!(&self.col, &other.col); + let mut result = cmatrix(self.data.clone(), self.row, self.col, self.shape); + for i in 0..self.row { + for j in 0..self.col { + result[(i, j)] -= other[(i, j)]; + } + } + result + } +} + +impl<'a, 'b> Sub<&'b ComplexMatrix> for &'a ComplexMatrix { + type Output = ComplexMatrix; + + fn sub(self, rhs: &'b ComplexMatrix) -> Self::Output { + self.sub_vec(rhs) + } +} + +/// Subtraction between Complex Matrix & C64 +impl Sub for ComplexMatrix +where + T: Into + Copy, +{ + type Output = Self; + + fn sub(self, other: T) -> Self::Output { + self.fmap(|x| x - other.into()) + } +} + +/// Subtraction between &Complex Matrix & C64 +impl<'a, T> Sub for &'a ComplexMatrix +where + T: Into + Copy, +{ + type Output = ComplexMatrix; + + fn sub(self, other: T) -> Self::Output { + self.fmap(|x| x - other.into()) + } +} + +/// Subtraction Complex Matrix with C64 +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let mut a = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 4.0+4.0i 5.0+5.0i"); +/// let a_exp = ml_cmatrix("0.0+0.0i 1.0+1.0i; +/// 3.0+3.0i 4.0+4.0i"); +/// assert_eq!(a - C64::new(1_f64, 1_f64), a_exp); +/// } +/// ``` +impl Sub for C64 { + type Output = ComplexMatrix; + + fn sub(self, other: ComplexMatrix) -> Self::Output { + -other.sub(self) + } +} + +impl<'a> Sub<&'a ComplexMatrix> for f64 { + type Output = ComplexMatrix; + + fn sub(self, other: &'a ComplexMatrix) -> Self::Output { + -other.sub(self) + } +} + +// ============================================================================= +// Multiplication for Complex Matrix +// ============================================================================= +/// Element-wise multiplication between Complex Matrix vs C64 +impl Mul for ComplexMatrix { + type Output = Self; + + fn mul(self, other: C64) -> Self::Output { + self.fmap(|x| x * other) + } +} + +impl Mul for C64 { + type Output = ComplexMatrix; + + fn mul(self, other: ComplexMatrix) -> Self::Output { + other.mul(self) + } +} + +impl<'a> Mul<&'a ComplexMatrix> for C64 { + type Output = ComplexMatrix; + + fn mul(self, other: &'a ComplexMatrix) -> Self::Output { + other.mul_scalar(self) + } +} + +/// Matrix Multiplication +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let mut a = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 4.0+4.0i 5.0+5.0i"); +/// let mut b = ml_cmatrix("2.0+2.0i 2.0+2.0i; +/// 5.0+5.0i 5.0+5.0i"); +/// let prod = ml_cmatrix("0.0+24.0i 0.0+24.0i; +/// 0.0+66.0i 0.0+66.0i"); +/// assert_eq!(a * b, prod); +/// } +/// ``` +impl Mul for ComplexMatrix { + type Output = Self; + + fn mul(self, other: Self) -> Self::Output { + cmatmul(&self, &other) + } +} + +impl<'a, 'b> Mul<&'b ComplexMatrix> for &'a ComplexMatrix { + type Output = ComplexMatrix; + + fn mul(self, other: &'b ComplexMatrix) -> Self::Output { + cmatmul(self, other) + } +} + +#[allow(non_snake_case)] +impl Mul> for ComplexMatrix { + type Output = Vec; + + fn mul(self, other: Vec) -> Self::Output { + self.apply(&other) + } +} + +#[allow(non_snake_case)] +impl<'a, 'b> Mul<&'b Vec> for &'a ComplexMatrix { + type Output = Vec; + + fn mul(self, other: &'b Vec) -> Self::Output { + self.apply(other) + } +} + +/// Matrix multiplication for `Vec` vs `ComplexMatrix` +impl Mul for Vec { + type Output = Vec; + + fn mul(self, other: ComplexMatrix) -> Self::Output { + assert_eq!(self.len(), other.row); + let mut c = vec![Complex::zero(); other.col]; + complex_gevm(Complex::one(), &self, &other, Complex::zero(), &mut c); + c + } +} + +impl<'a, 'b> Mul<&'b ComplexMatrix> for &'a Vec { + type Output = Vec; + + fn mul(self, other: &'b ComplexMatrix) -> Self::Output { + assert_eq!(self.len(), other.row); + let mut c = vec![Complex::zero(); other.col]; + complex_gevm(Complex::one(), self, other, Complex::zero(), &mut c); + c + } +} + +// ============================================================================= +// Standard Operation for Matrix (DIV) +// ============================================================================= +/// Element-wise division between Complex Matrix vs C64 +impl Div for ComplexMatrix { + type Output = Self; + + fn div(self, other: C64) -> Self::Output { + self.fmap(|x| x / other) + } +} + +impl<'a> Div for &'a ComplexMatrix { + type Output = ComplexMatrix; + + fn div(self, other: C64) -> Self::Output { + self.fmap(|x| x / other) + } +} + +/// Index for Complex Matrix +/// +/// `(usize, usize) -> C64` +/// +/// # Examples +/// ```rust +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// let a = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row +/// ); +/// assert_eq!(a[(0,1)], C64::new(2f64, 2f64)); +/// ``` +impl Index<(usize, usize)> for ComplexMatrix { + type Output = C64; + + fn index(&self, pair: (usize, usize)) -> &C64 { + let p = self.ptr(); + let i = pair.0; + let j = pair.1; + assert!(i < self.row && j < self.col, "Index out of range"); + match self.shape { + Shape::Row => unsafe { &*p.add(i * self.col + j) }, + Shape::Col => unsafe { &*p.add(i + j * self.row) }, + } + } +} + +/// IndexMut for Complex Matrix (Assign) +/// +/// `(usize, usize) -> C64` +/// +/// # Examples +/// ```rust +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// let mut a = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row +/// ); +/// assert_eq!(a[(0,1)], C64::new(2f64, 2f64)); +/// ``` +impl IndexMut<(usize, usize)> for ComplexMatrix { + fn index_mut(&mut self, pair: (usize, usize)) -> &mut C64 { + let i = pair.0; + let j = pair.1; + let r = self.row; + let c = self.col; + assert!(i < self.row && j < self.col, "Index out of range"); + let p = self.mut_ptr(); + match self.shape { + Shape::Row => { + let idx = i * c + j; + unsafe { &mut *p.add(idx) } + } + Shape::Col => { + let idx = i + j * r; + unsafe { &mut *p.add(idx) } + } + } + } +} + +// ============================================================================= +// Functional Programming Tools (Hand-written) +// ============================================================================= + +impl FPMatrix for ComplexMatrix { + type Scalar = C64; + + fn take_row(&self, n: usize) -> Self { + if n >= self.row { + return self.clone(); + } + match self.shape { + Shape::Row => { + let new_data = self + .data + .clone() + .into_iter() + .take(n * self.col) + .collect::>(); + cmatrix(new_data, n, self.col, Shape::Row) + } + Shape::Col => { + let mut temp_data: Vec = Vec::new(); + for i in 0..n { + temp_data.extend(self.row(i)); + } + cmatrix(temp_data, n, self.col, Shape::Row) + } + } + } + + fn take_col(&self, n: usize) -> Self { + if n >= self.col { + return self.clone(); + } + match self.shape { + Shape::Col => { + let new_data = self + .data + .clone() + .into_iter() + .take(n * self.row) + .collect::>(); + cmatrix(new_data, self.row, n, Shape::Col) + } + Shape::Row => { + let mut temp_data: Vec = Vec::new(); + for i in 0..n { + temp_data.extend(self.col(i)); + } + cmatrix(temp_data, self.row, n, Shape::Col) + } + } + } + + fn skip_row(&self, n: usize) -> Self { + assert!(n < self.row, "Skip range is larger than row of matrix"); + + let mut temp_data: Vec = Vec::new(); + for i in n..self.row { + temp_data.extend(self.row(i)); + } + cmatrix(temp_data, self.row - n, self.col, Shape::Row) + } + + fn skip_col(&self, n: usize) -> Self { + assert!(n < self.col, "Skip range is larger than col of matrix"); + + let mut temp_data: Vec = Vec::new(); + for i in n..self.col { + temp_data.extend(self.col(i)); + } + cmatrix(temp_data, self.row, self.col - n, Shape::Col) + } + + fn fmap(&self, f: F) -> Self + where + F: Fn(C64) -> C64, + { + let result = self.data.iter().map(|x| f(*x)).collect::>(); + cmatrix(result, self.row, self.col, self.shape) + } + + /// Column map + /// + /// # Example + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// use peroxide::traits::fp::FPMatrix; + /// + /// fn main() { + /// let x = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// let y = x.col_map(|r| r.fmap(|t| t + r[0])); + /// + /// let y_col_map = cmatrix(vec![C64::new(2f64, 2f64), + /// C64::new(4f64, 4f64), + /// C64::new(4f64, 4f64), + /// C64::new(6f64, 6f64)], + /// 2, 2, Col + /// ); + /// + /// assert_eq!(y, y_col_map); + /// } + /// ``` + fn col_map(&self, f: F) -> ComplexMatrix + where + F: Fn(Vec) -> Vec, + { + let mut result = cmatrix( + vec![Complex::zero(); self.row * self.col], + self.row, + self.col, + Shape::Col, + ); + + for i in 0..self.col { + result.subs_col(i, &f(self.col(i))); + } + + result + } + + /// Row map + /// + /// # Example + /// ```rust + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// use peroxide::traits::fp::FPMatrix; + /// + /// fn main() { + /// let x = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// let y = x.row_map(|r| r.fmap(|t| t + r[0])); + /// + /// let y_row_map = cmatrix(vec![C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(6f64, 6f64), + /// C64::new(7f64, 7f64)], + /// 2, 2, Row + /// ); + /// + /// assert_eq!(y, y_row_map); + /// } + /// ``` + fn row_map(&self, f: F) -> ComplexMatrix + where + F: Fn(Vec) -> Vec, + { + let mut result = cmatrix( + vec![Complex::zero(); self.row * self.col], + self.row, + self.col, + Shape::Row, + ); + + for i in 0..self.row { + result.subs_row(i, &f(self.row(i))); + } + + result + } + + fn col_mut_map(&mut self, f: F) + where + F: Fn(Vec) -> Vec, + { + for i in 0..self.col { + unsafe { + let mut p = self.col_mut(i); + let fv = f(self.col(i)); + for j in 0..p.len() { + *p[j] = fv[j]; + } + } + } + } + + fn row_mut_map(&mut self, f: F) + where + F: Fn(Vec) -> Vec, + { + for i in 0..self.col { + unsafe { + let mut p = self.row_mut(i); + let fv = f(self.row(i)); + for j in 0..p.len() { + *p[j] = fv[j]; + } + } + } + } + + fn reduce(&self, init: T, f: F) -> C64 + where + F: Fn(C64, C64) -> C64, + T: Into, + { + self.data.iter().fold(init.into(), |x, y| f(x, *y)) + } + + fn zip_with(&self, f: F, other: &ComplexMatrix) -> Self + where + F: Fn(C64, C64) -> C64, + { + assert_eq!(self.data.len(), other.data.len()); + let mut a = other.clone(); + if self.shape != other.shape { + a = a.change_shape(); + } + let result = self + .data + .iter() + .zip(a.data.iter()) + .map(|(x, y)| f(*x, *y)) + .collect::>(); + cmatrix(result, self.row, self.col, self.shape) + } + + fn col_reduce(&self, f: F) -> Vec + where + F: Fn(Vec) -> C64, + { + let mut v = vec![Complex::zero(); self.col]; + for i in 0..self.col { + v[i] = f(self.col(i)); + } + v + } + + fn row_reduce(&self, f: F) -> Vec + where + F: Fn(Vec) -> C64, + { + let mut v = vec![Complex::zero(); self.row]; + for i in 0..self.row { + v[i] = f(self.row(i)); + } + v + } +} + +pub fn cdiag(n: usize) -> ComplexMatrix { + let mut v: Vec = vec![Complex::zero(); n * n]; + for i in 0..n { + let idx = i * (n + 1); + v[idx] = Complex::one(); + } + cmatrix(v, n, n, Shape::Row) +} + +impl PQLU { + /// Extract PQLU + /// + /// # Usage + /// ```rust + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// + /// let a = cmatrix(vec![C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64)], + /// 2, 2, Row + /// ); + /// let pqlu = a.lu(); + /// let (p, q, l, u) = pqlu.extract(); + /// // p, q are permutations + /// // l, u are matrices + /// println!("{}", l); // lower triangular + /// println!("{}", u); // upper triangular + /// ``` + pub fn extract(&self) -> (Vec, Vec, ComplexMatrix, ComplexMatrix) { + ( + self.p.clone(), + self.q.clone(), + self.l.clone(), + self.u.clone(), + ) + } + + pub fn det(&self) -> C64 { + // sgn of perms + let mut sgn_p = 1f64; + let mut sgn_q = 1f64; + for (i, &j) in self.p.iter().enumerate() { + if i != j { + sgn_p *= -1f64; + } + } + for (i, &j) in self.q.iter().enumerate() { + if i != j { + sgn_q *= -1f64; + } + } + + self.u.diag().reduce(Complex::one(), |x, y| x * y) * sgn_p * sgn_q + } + + pub fn inv(&self) -> ComplexMatrix { + let (p, q, l, u) = self.extract(); + let mut m = complex_inv_u(u) * complex_inv_l(l); + // Q = Q1 Q2 Q3 .. + for (idx1, idx2) in q.into_iter().enumerate().rev() { + unsafe { + m.swap(idx1, idx2, Shape::Row); + } + } + // P = Pn-1 .. P3 P2 P1 + for (idx1, idx2) in p.into_iter().enumerate().rev() { + unsafe { + m.swap(idx1, idx2, Shape::Col); + } + } + m + } +} + +/// MATLAB like eye - Identity matrix +pub fn ceye(n: usize) -> ComplexMatrix { + let mut m = cmatrix(vec![Complex::zero(); n * n], n, n, Shape::Row); + for i in 0..n { + m[(i, i)] = Complex::one(); + } + m +} + +// ============================================================================= +// Linear Algebra +// ============================================================================= + +impl LinearAlgebra for ComplexMatrix { + /// Backward Substitution for Upper Triangular + fn back_subs(&self, b: &[C64]) -> Vec { + let n = self.col; + let mut y = vec![Complex::zero(); n]; + y[n - 1] = b[n - 1] / self[(n - 1, n - 1)]; + for i in (0..n - 1).rev() { + let mut s = Complex::zero(); + for j in i + 1..n { + s += self[(i, j)] * y[j]; + } + y[i] = 1f64 / self[(i, i)] * (b[i] - s); + } + y + } + + /// Forward substitution for Lower Triangular + fn forward_subs(&self, b: &[C64]) -> Vec { + let n = self.col; + let mut y = vec![Complex::zero(); n]; + y[0] = b[0] / self[(0, 0)]; + for i in 1..n { + let mut s = Complex::zero(); + for j in 0..i { + s += self[(i, j)] * y[j]; + } + y[i] = 1f64 / self[(i, i)] * (b[i] - s); + } + y + } + + /// LU Decomposition Implements (Complete Pivot) + /// + /// # Description + /// It use complete pivoting LU decomposition. + /// You can get two permutations, and LU matrices. + /// + /// # Caution + /// It returns `Option` - You should unwrap to obtain real value. + /// `PQLU` has four field - `p`, `q`, `l`, `u`. + /// `p`, `q` are permutations. + /// `l`, `u` are matrices. + /// + /// # Examples + /// ``` + /// #[macro_use] + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = cmatrix(vec![ + /// C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64) + /// ], + /// 2, 2, Row + /// ); + /// + /// let l_exp = cmatrix(vec![ + /// C64::new(1f64, 0f64), + /// C64::new(0f64, 0f64), + /// C64::new(0.5f64, -0.0f64), + /// C64::new(1f64, 0f64) + /// ], + /// 2, 2, Row + /// ); + /// + /// let u_exp = cmatrix(vec![ + /// C64::new(4f64, 4f64), + /// C64::new(3f64, 3f64), + /// C64::new(0f64, 0f64), + /// C64::new(-0.5f64, -0.5f64) + /// ], + /// 2, 2, Row + /// ); + /// let pqlu = a.lu(); + /// let (p,q,l,u) = (pqlu.p, pqlu.q, pqlu.l, pqlu.u); + /// assert_eq!(p, vec![1]); // swap 0 & 1 (Row) + /// assert_eq!(q, vec![1]); // swap 0 & 1 (Col) + /// assert_eq!(l, l_exp); + /// assert_eq!(u, u_exp); + /// } + /// ``` + fn lu(&self) -> PQLU { + assert_eq!(self.col, self.row); + let n = self.row; + let len: usize = n * n; + + let mut l = ceye(n); + let mut u = cmatrix(vec![Complex::zero(); len], n, n, self.shape); + + let mut temp = self.clone(); + let (p, q) = gecp(&mut temp); + for i in 0..n { + for j in 0..i { + // Inverse multiplier + l[(i, j)] = -temp[(i, j)]; + } + for j in i..n { + u[(i, j)] = temp[(i, j)]; + } + } + // Pivoting L + for i in 0..n - 1 { + unsafe { + let l_i = l.col_mut(i); + for j in i + 1..l.col - 1 { + let dst = p[j]; + std::ptr::swap(l_i[j], l_i[dst]); + } + } + } + PQLU { p, q, l, u } + } + + fn waz(&self, _d_form: Form) -> Option> { + unimplemented!() + } + + fn qr(&self) -> QR { + unimplemented!() + } + + fn svd(&self) -> SVD { + unimplemented!() + } + + #[cfg(feature = "O3")] + fn cholesky(&self, uplo: UPLO) -> ComplexMatrix { + unimplemented!() + } + + fn rref(&self) -> ComplexMatrix { + unimplemented!() + } + + /// Determinant + /// + /// # Examples + /// ``` + /// #[macro_use] + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = cmatrix(vec![ + /// C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64) + /// ], + /// 2, 2, Row + /// ); + /// assert_eq!(a.det().norm(), 4f64); + /// } + /// ``` + fn det(&self) -> C64 { + assert_eq!(self.row, self.col); + self.lu().det() + } + + /// Block Partition + /// + /// # Examples + /// ```rust + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// let a = cmatrix(vec![ + /// C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64) + /// ], + /// 2, 2, Row + /// ); + /// let (m1, m2, m3, m4) = a.block(); + /// assert_eq!(m1, ml_cmatrix("1.0+1.0i")); + /// assert_eq!(m2, ml_cmatrix("2.0+2.0i")); + /// assert_eq!(m3, ml_cmatrix("3.0+3.0i")); + /// assert_eq!(m4, ml_cmatrix("4.0+4.0i")); + /// } + /// ``` + fn block(&self) -> (Self, Self, Self, Self) { + let r = self.row; + let c = self.col; + let l_r = self.row / 2; + let l_c = self.col / 2; + let r_l = r - l_r; + let c_l = c - l_c; + + let mut m1 = cmatrix(vec![Complex::zero(); l_r * l_c], l_r, l_c, self.shape); + let mut m2 = cmatrix(vec![Complex::zero(); l_r * c_l], l_r, c_l, self.shape); + let mut m3 = cmatrix(vec![Complex::zero(); r_l * l_c], r_l, l_c, self.shape); + let mut m4 = cmatrix(vec![Complex::zero(); r_l * c_l], r_l, c_l, self.shape); + + for idx_row in 0..r { + for idx_col in 0..c { + match (idx_row, idx_col) { + (i, j) if (i < l_r) && (j < l_c) => { + m1[(i, j)] = self[(i, j)]; + } + (i, j) if (i < l_r) && (j >= l_c) => { + m2[(i, j - l_c)] = self[(i, j)]; + } + (i, j) if (i >= l_r) && (j < l_c) => { + m3[(i - l_r, j)] = self[(i, j)]; + } + (i, j) if (i >= l_r) && (j >= l_c) => { + m4[(i - l_r, j - l_c)] = self[(i, j)]; + } + _ => (), + } + } + } + (m1, m2, m3, m4) + } + + /// Inverse of Matrix + /// + /// # Caution + /// + /// `inv` function returns `Option` + /// Thus, you should use pattern matching or `unwrap` to obtain inverse. + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::complex::matrix::*; + /// + /// fn main() { + /// // Non-singular + /// let a = cmatrix(vec![ + /// C64::new(1f64, 1f64), + /// C64::new(2f64, 2f64), + /// C64::new(3f64, 3f64), + /// C64::new(4f64, 4f64) + /// ], + /// 2, 2, Row + /// ); + /// + /// let a_inv_exp = cmatrix(vec![ + /// C64::new(-1.0f64, 1f64), + /// C64::new(0.5f64, -0.5f64), + /// C64::new(0.75f64, -0.75f64), + /// C64::new(-0.25f64, 0.25f64) + /// ], + /// 2, 2, Row + /// ); + /// assert_eq!(a.inv(), a_inv_exp); + /// } + /// ``` + fn inv(&self) -> Self { + self.lu().inv() + } + + fn pseudo_inv(&self) -> ComplexMatrix { + unimplemented!() + } + + /// Solve with Vector + /// + /// # Solve options + /// + /// * LU: Gaussian elimination with Complete pivoting LU (GECP) + /// * WAZ: Solve with WAZ decomposition + fn solve(&self, b: &[C64], sk: SolveKind) -> Vec { + match sk { + SolveKind::LU => { + let lu = self.lu(); + let (p, q, l, u) = lu.extract(); + let mut v = b.to_vec(); + v.swap_with_perm(&p.into_iter().enumerate().collect()); + let z = l.forward_subs(&v); + let mut y = u.back_subs(&z); + y.swap_with_perm(&q.into_iter().enumerate().rev().collect()); + y + } + SolveKind::WAZ => { + unimplemented!() + } + } + } + + fn solve_mat(&self, m: &ComplexMatrix, sk: SolveKind) -> ComplexMatrix { + match sk { + SolveKind::LU => { + let lu = self.lu(); + let (p, q, l, u) = lu.extract(); + let mut x = cmatrix( + vec![Complex::zero(); self.col * m.col], + self.col, + m.col, + Shape::Col, + ); + for i in 0..m.col { + let mut v = m.col(i).clone(); + for (r, &s) in p.iter().enumerate() { + v.swap(r, s); + } + let z = l.forward_subs(&v); + let mut y = u.back_subs(&z); + for (r, &s) in q.iter().enumerate() { + y.swap(r, s); + } + unsafe { + let mut c = x.col_mut(i); + copy_vec_ptr(&mut c, &y); + } + } + x + } + SolveKind::WAZ => { + unimplemented!() + } + } + } + + fn is_symmetric(&self) -> bool { + if self.row != self.col { + return false; + } + + for i in 0..self.row { + for j in i..self.col { + if (!nearly_eq(self[(i, j)].re, self[(j, i)].re)) + && (!nearly_eq(self[(i, j)].im, self[(j, i)].im)) + { + return false; + } + } + } + true + } +} + +#[allow(non_snake_case)] +pub fn csolve(A: &ComplexMatrix, b: &ComplexMatrix, sk: SolveKind) -> ComplexMatrix { + A.solve_mat(b, sk) +} + +impl MutMatrix for ComplexMatrix { + type Scalar = C64; + + unsafe fn col_mut(&mut self, idx: usize) -> Vec<*mut C64> { + assert!(idx < self.col, "Index out of range"); + match self.shape { + Shape::Col => { + let mut v: Vec<*mut C64> = vec![&mut Complex::zero(); self.row]; + let start_idx = idx * self.row; + let p = self.mut_ptr(); + for (i, j) in (start_idx..start_idx + v.len()).enumerate() { + v[i] = p.add(j); + } + v + } + Shape::Row => { + let mut v: Vec<*mut C64> = vec![&mut Complex::zero(); self.row]; + let p = self.mut_ptr(); + for i in 0..v.len() { + v[i] = p.add(idx + i * self.col); + } + v + } + } + } + + unsafe fn row_mut(&mut self, idx: usize) -> Vec<*mut C64> { + assert!(idx < self.row, "Index out of range"); + match self.shape { + Shape::Row => { + let mut v: Vec<*mut C64> = vec![&mut Complex::zero(); self.col]; + let start_idx = idx * self.col; + let p = self.mut_ptr(); + for (i, j) in (start_idx..start_idx + v.len()).enumerate() { + v[i] = p.add(j); + } + v + } + Shape::Col => { + let mut v: Vec<*mut C64> = vec![&mut Complex::zero(); self.col]; + let p = self.mut_ptr(); + for i in 0..v.len() { + v[i] = p.add(idx + i * self.row); + } + v + } + } + } + + unsafe fn swap(&mut self, idx1: usize, idx2: usize, shape: Shape) { + match shape { + Shape::Col => swap_vec_ptr(&mut self.col_mut(idx1), &mut self.col_mut(idx2)), + Shape::Row => swap_vec_ptr(&mut self.row_mut(idx1), &mut self.row_mut(idx2)), + } + } + + unsafe fn swap_with_perm(&mut self, p: &Vec<(usize, usize)>, shape: Shape) { + for (i, j) in p.iter() { + self.swap(*i, *j, shape) + } + } +} + +impl ExpLogOps for ComplexMatrix { + type Float = C64; + + fn exp(&self) -> Self { + self.fmap(|x| x.exp()) + } + fn ln(&self) -> Self { + self.fmap(|x| x.ln()) + } + fn log(&self, base: Self::Float) -> Self { + self.fmap(|x| x.ln() / base.ln()) // Using `Log: change of base` formula + } + fn log2(&self) -> Self { + self.fmap(|x| x.ln() / 2.0.ln()) // Using `Log: change of base` formula + } + fn log10(&self) -> Self { + self.fmap(|x| x.ln() / 10.0.ln()) // Using `Log: change of base` formula + } +} + +impl PowOps for ComplexMatrix { + type Float = C64; + + fn powi(&self, n: i32) -> Self { + self.fmap(|x| x.powi(n)) + } + + fn powf(&self, f: Self::Float) -> Self { + self.fmap(|x| x.powc(f)) + } + + fn pow(&self, _f: Self) -> Self { + unimplemented!() + } + + fn sqrt(&self) -> Self { + self.fmap(|x| x.sqrt()) + } +} + +impl TrigOps for ComplexMatrix { + fn sin_cos(&self) -> (Self, Self) { + let (sin, cos) = self.data.iter().map(|x| (x.sin(), x.cos())).unzip(); + ( + cmatrix(sin, self.row, self.col, self.shape), + cmatrix(cos, self.row, self.col, self.shape), + ) + } + + fn sin(&self) -> Self { + self.fmap(|x| x.sin()) + } + + fn cos(&self) -> Self { + self.fmap(|x| x.cos()) + } + + fn tan(&self) -> Self { + self.fmap(|x| x.tan()) + } + + fn sinh(&self) -> Self { + self.fmap(|x| x.sinh()) + } + + fn cosh(&self) -> Self { + self.fmap(|x| x.cosh()) + } + + fn tanh(&self) -> Self { + self.fmap(|x| x.tanh()) + } + + fn asin(&self) -> Self { + self.fmap(|x| x.asin()) + } + + fn acos(&self) -> Self { + self.fmap(|x| x.acos()) + } + + fn atan(&self) -> Self { + self.fmap(|x| x.atan()) + } + + fn asinh(&self) -> Self { + self.fmap(|x| x.asinh()) + } + + fn acosh(&self) -> Self { + self.fmap(|x| x.acosh()) + } + + fn atanh(&self) -> Self { + self.fmap(|x| x.atanh()) + } +} + +// ============================================================================= +// Back-end Utils +// ============================================================================= +/// Combine separated Complex Matrix to one Complex Matrix +/// +/// # Examples +/// ```rust +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// use peroxide::traits::fp::FPMatrix; +/// +/// fn main() { +/// let x1 = cmatrix(vec![C64::new(1f64, 1f64)], 1, 1, Row); +/// let x2 = cmatrix(vec![C64::new(2f64, 2f64)], 1, 1, Row); +/// let x3 = cmatrix(vec![C64::new(3f64, 3f64)], 1, 1, Row); +/// let x4 = cmatrix(vec![C64::new(4f64, 4f64)], 1, 1, Row); +/// +/// let y = complex_combine(x1, x2, x3, x4); +/// +/// let y_exp = cmatrix(vec![C64::new(1f64, 1f64), +/// C64::new(2f64, 2f64), +/// C64::new(3f64, 3f64), +/// C64::new(4f64, 4f64)], +/// 2, 2, Row +/// ); +/// +/// assert_eq!(y, y_exp); +/// } +/// ``` +pub fn complex_combine( + m1: ComplexMatrix, + m2: ComplexMatrix, + m3: ComplexMatrix, + m4: ComplexMatrix, +) -> ComplexMatrix { + let l_r = m1.row; + let l_c = m1.col; + let c_l = m2.col; + let r_l = m3.row; + + let r = l_r + r_l; + let c = l_c + c_l; + + let mut m = cmatrix(vec![Complex::zero(); r * c], r, c, m1.shape); + + for idx_row in 0..r { + for idx_col in 0..c { + match (idx_row, idx_col) { + (i, j) if (i < l_r) && (j < l_c) => { + m[(i, j)] = m1[(i, j)]; + } + (i, j) if (i < l_r) && (j >= l_c) => { + m[(i, j)] = m2[(i, j - l_c)]; + } + (i, j) if (i >= l_r) && (j < l_c) => { + m[(i, j)] = m3[(i - l_r, j)]; + } + (i, j) if (i >= l_r) && (j >= l_c) => { + m[(i, j)] = m4[(i - l_r, j - l_c)]; + } + _ => (), + } + } + } + m +} + +/// Inverse of Lower matrix +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let a = ml_cmatrix("2.0+2.0i 0.0+0.0i; +/// 2.0+2.0i 1.0+1.0i"); +/// let b = cmatrix(vec![C64::new(2f64, 2f64), +/// C64::new(0f64, 0f64), +/// C64::new(-2f64, -2f64), +/// C64::new(1f64, 1f64)], +/// 2, 2, Row +/// ); +/// assert_eq!(complex_inv_l(a), b); +/// } +/// ``` +pub fn complex_inv_l(l: ComplexMatrix) -> ComplexMatrix { + let mut m = l.clone(); + + match l.row { + 1 => l, + 2 => { + m[(1, 0)] = -m[(1, 0)]; + m + } + _ => { + let (l1, l2, l3, l4) = l.block(); + + let m1 = complex_inv_l(l1); + let m2 = l2; + let m4 = complex_inv_l(l4); + let m3 = -(&(&m4 * &l3) * &m1); + + complex_combine(m1, m2, m3, m4) + } + } +} + +/// Inverse of upper triangular matrix +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let a = ml_cmatrix("2.0+2.0i 2.0+2.0i; +/// 0.0+0.0i 1.0+1.0i"); +/// let b = cmatrix(vec![C64::new(0.25f64, -0.25f64), +/// C64::new(-0.5f64, 0.5f64), +/// C64::new(0.0f64, 0.0f64), +/// C64::new(0.5f64, -0.5f64)], +/// 2, 2, Row +/// ); +/// assert_eq!(complex_inv_u(a), b); +/// } +/// ``` +pub fn complex_inv_u(u: ComplexMatrix) -> ComplexMatrix { + let mut w = u.clone(); + + match u.row { + 1 => { + w[(0, 0)] = 1f64 / w[(0, 0)]; + w + } + 2 => { + let a = w[(0, 0)]; + let b = w[(0, 1)]; + let c = w[(1, 1)]; + let d = a * c; + + w[(0, 0)] = 1f64 / a; + w[(0, 1)] = -b / d; + w[(1, 1)] = 1f64 / c; + w + } + _ => { + let (u1, u2, u3, u4) = u.block(); + let m1 = complex_inv_u(u1); + let m3 = u3; + let m4 = complex_inv_u(u4); + let m2 = -(m1.clone() * u2 * m4.clone()); + + complex_combine(m1, m2, m3, m4) + } + } +} + +/// Matrix multiply back-ends +pub fn cmatmul(a: &ComplexMatrix, b: &ComplexMatrix) -> ComplexMatrix { + assert_eq!(a.col, b.row); + let mut c = cmatrix(vec![Complex::zero(); a.row * b.col], a.row, b.col, a.shape); + cgemm(Complex::one(), a, b, Complex::zero(), &mut c); + c +} + +/// GEMM wrapper for Matrixmultiply +/// +/// # Examples +/// ```rust +/// #[macro_use] +/// extern crate peroxide; +/// use peroxide::fuga::*; +/// +/// use peroxide::complex::matrix::*; +/// +/// fn main() { +/// let a = ml_cmatrix("1.0+1.0i 2.0+2.0i; +/// 0.0+0.0i 1.0+1.0i"); +/// let b = ml_cmatrix("1.0+1.0i 0.0+0.0i; +/// 2.0+2.0i 1.0+1.0i"); +/// let mut c1 = ml_cmatrix("1.0+1.0i 1.0+1.0i; +/// 1.0+1.0i 1.0+1.0i"); +/// let mul_val = ml_cmatrix("-10.0+10.0i -4.0+4.0i; +/// -4.0+4.0i -2.0+2.0i"); +/// +/// cgemm(C64::new(1.0, 1.0), &a, &b, C64::new(0.0, 0.0), &mut c1); +/// assert_eq!(c1, mul_val); +/// } +pub fn cgemm(alpha: C64, a: &ComplexMatrix, b: &ComplexMatrix, beta: C64, c: &mut ComplexMatrix) { + let m = a.row; + let k = a.col; + let n = b.col; + let (rsa, csa) = match a.shape { + Shape::Row => (a.col as isize, 1isize), + Shape::Col => (1isize, a.row as isize), + }; + let (rsb, csb) = match b.shape { + Shape::Row => (b.col as isize, 1isize), + Shape::Col => (1isize, b.row as isize), + }; + let (rsc, csc) = match c.shape { + Shape::Row => (c.col as isize, 1isize), + Shape::Col => (1isize, c.row as isize), + }; + + unsafe { + matrixmultiply::zgemm( + // Requires crate feature "cgemm" + CGemmOption::Standard, + CGemmOption::Standard, + m, + k, + n, + [alpha.re, alpha.im], + a.ptr() as *const _, + rsa, + csa, + b.ptr() as *const _, + rsb, + csb, + [beta.re, beta.im], + c.mut_ptr() as *mut _, + rsc, + csc, + ) + } +} + +/// General Matrix-Vector multiplication +pub fn cgemv(alpha: C64, a: &ComplexMatrix, b: &Vec, beta: C64, c: &mut Vec) { + let m = a.row; + let k = a.col; + let n = 1usize; + let (rsa, csa) = match a.shape { + Shape::Row => (a.col as isize, 1isize), + Shape::Col => (1isize, a.row as isize), + }; + let (rsb, csb) = (1isize, 1isize); + let (rsc, csc) = (1isize, 1isize); + + unsafe { + matrixmultiply::zgemm( + // Requires crate feature "cgemm" + CGemmOption::Standard, + CGemmOption::Standard, + m, + k, + n, + [alpha.re, alpha.im], + a.ptr() as *const _, + rsa, + csa, + b.as_ptr() as *const _, + rsb, + csb, + [beta.re, beta.im], + c.as_mut_ptr() as *mut _, + rsc, + csc, + ) + } +} + +/// General Vector-Matrix multiplication +pub fn complex_gevm(alpha: C64, a: &Vec, b: &ComplexMatrix, beta: C64, c: &mut Vec) { + let m = 1usize; + let k = a.len(); + let n = b.col; + let (rsa, csa) = (1isize, 1isize); + let (rsb, csb) = match b.shape { + Shape::Row => (b.col as isize, 1isize), + Shape::Col => (1isize, b.row as isize), + }; + let (rsc, csc) = (1isize, 1isize); + + unsafe { + matrixmultiply::zgemm( + // Requires crate feature "cgemm" + CGemmOption::Standard, + CGemmOption::Standard, + m, + k, + n, + [alpha.re, alpha.im], + a.as_ptr() as *const _, + rsa, + csa, + b.ptr() as *const _, + rsb, + csb, + [beta.re, beta.im], + c.as_mut_ptr() as *mut _, + rsc, + csc, + ) + } +} + +/// LU via Gaussian Elimination with Partial Pivoting +#[allow(dead_code)] +fn gepp(m: &mut ComplexMatrix) -> Vec { + let mut r = vec![0usize; m.col - 1]; + for k in 0..(m.col - 1) { + // Find the pivot row + let r_k = m + .col(k) + .into_iter() + .skip(k) + .enumerate() + .max_by(|x1, x2| x1.1.norm().partial_cmp(&x2.1.norm()).unwrap()) + .unwrap() + .0 + + k; + r[k] = r_k; + + // Interchange the rows r_k and k + for j in k..m.col { + unsafe { + std::ptr::swap(&mut m[(k, j)], &mut m[(r_k, j)]); + println!("Swap! k:{}, r_k:{}", k, r_k); + } + } + // Form the multipliers + for i in k + 1..m.col { + m[(i, k)] = -m[(i, k)] / m[(k, k)]; + } + // Update the entries + for i in k + 1..m.col { + for j in k + 1..m.col { + let local_m = m[(i, k)] * m[(k, j)]; + m[(i, j)] += local_m; + } + } + } + r +} + +/// LU via Gauss Elimination with Complete Pivoting +fn gecp(m: &mut ComplexMatrix) -> (Vec, Vec) { + let n = m.col; + let mut r = vec![0usize; n - 1]; + let mut s = vec![0usize; n - 1]; + for k in 0..n - 1 { + // Find pivot + let (r_k, s_k) = match m.shape { + Shape::Col => { + let mut row_ics = 0usize; + let mut col_ics = 0usize; + let mut max_val = 0f64; + for i in k..n { + let c = m + .col(i) + .into_iter() + .skip(k) + .enumerate() + .max_by(|x1, x2| x1.1.norm().partial_cmp(&x2.1.norm()).unwrap()) + .unwrap(); + let c_ics = c.0 + k; + let c_val = c.1.norm(); + if c_val > max_val { + row_ics = c_ics; + col_ics = i; + max_val = c_val; + } + } + (row_ics, col_ics) + } + Shape::Row => { + let mut row_ics = 0usize; + let mut col_ics = 0usize; + let mut max_val = 0f64; + for i in k..n { + let c = m + .row(i) + .into_iter() + .skip(k) + .enumerate() + .max_by(|x1, x2| x1.1.norm().partial_cmp(&x2.1.norm()).unwrap()) + .unwrap(); + let c_ics = c.0 + k; + let c_val = c.1.norm(); + if c_val > max_val { + col_ics = c_ics; + row_ics = i; + max_val = c_val; + } + } + (row_ics, col_ics) + } + }; + r[k] = r_k; + s[k] = s_k; + + // Interchange rows + for j in k..n { + unsafe { + std::ptr::swap(&mut m[(k, j)], &mut m[(r_k, j)]); + } + } + + // Interchange cols + for i in 0..n { + unsafe { + std::ptr::swap(&mut m[(i, k)], &mut m[(i, s_k)]); + } + } + + // Form the multipliers + for i in k + 1..n { + m[(i, k)] = -m[(i, k)] / m[(k, k)]; + for j in k + 1..n { + let local_m = m[(i, k)] * m[(k, j)]; + m[(i, j)] += local_m; + } + } + } + (r, s) +} diff --git a/src/complex/mod.rs b/src/complex/mod.rs index 21f80b41..b17fc18f 100644 --- a/src/complex/mod.rs +++ b/src/complex/mod.rs @@ -2,4 +2,6 @@ use num_complex::Complex; pub type C64 = Complex; +pub mod integral; +pub mod matrix; pub mod vector; diff --git a/src/complex/vector.rs b/src/complex/vector.rs index 2900a4c6..e5836643 100644 --- a/src/complex/vector.rs +++ b/src/complex/vector.rs @@ -1,7 +1,8 @@ -use num_complex::Complex; +use crate::fuga::Algorithm; use crate::traits::fp::FPVector; -use crate::traits::math::{Vector, Normed, Norm, InnerProduct}; +use crate::traits::math::{InnerProduct, Norm, Normed, Vector}; use crate::traits::sugar::VecOps; +use num_complex::Complex; impl Vector for Complex { type Scalar = Self; @@ -31,7 +32,8 @@ impl Normed for Complex { fn normalize(&self, kind: Norm) -> Self where - Self: Sized { + Self: Sized, + { let n = self.norm(kind); self / n } @@ -48,26 +50,33 @@ impl FPVector for Vec> { fn fmap(&self, f: F) -> Self where - F: Fn(Self::Scalar) -> Self::Scalar { + F: Fn(Self::Scalar) -> Self::Scalar, + { self.iter().map(|&x| f(x)).collect() } fn zip_with(&self, f: F, other: &Self) -> Self where - F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar { - self.iter().zip(other.iter()).map(|(&x, &y)| f(x,y)).collect() + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, + { + self.iter() + .zip(other.iter()) + .map(|(&x, &y)| f(x, y)) + .collect() } fn reduce(&self, init: T, f: F) -> Self::Scalar where - F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, - T: Into { - self.iter().fold(init.into(), |x, &y| f(x,y)) + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, + T: Into, + { + self.iter().fold(init.into(), |x, &y| f(x, y)) } fn filter(&self, f: F) -> Self where - F: Fn(Self::Scalar) -> bool { + F: Fn(Self::Scalar) -> bool, + { self.iter().filter(|&x| f(*x)).cloned().collect() } @@ -110,13 +119,14 @@ impl Normed for Vec> { fn norm(&self, kind: Norm) -> Self::UnsignedScalar { match kind { Norm::L1 => self.iter().map(|x| Complex::::norm(*x).abs()).sum(), - _ => unimplemented!() + _ => unimplemented!(), } } - fn normalize(&self, kind: Norm) -> Self + fn normalize(&self, _kind: Norm) -> Self where - Self: Sized { + Self: Sized, + { unimplemented!() } } @@ -128,3 +138,37 @@ impl InnerProduct for Vec> { } impl VecOps for Vec> {} + +impl Algorithm for Vec> { + type Scalar = Complex; + + fn rank(&self) -> Vec { + unimplemented!() + } + + fn sign(&self) -> Complex { + unimplemented!() + } + + fn arg_max(&self) -> usize { + unimplemented!() + } + + fn arg_min(&self) -> usize { + unimplemented!() + } + + fn max(&self) -> Complex { + unimplemented!() + } + + fn min(&self) -> Complex { + unimplemented!() + } + + fn swap_with_perm(&mut self, p: &Vec<(usize, usize)>) { + for (i, j) in p.iter() { + self.swap(*i, *j); + } + } +} diff --git a/src/fuga/mod.rs b/src/fuga/mod.rs index 216dcbd8..6159073b 100644 --- a/src/fuga/mod.rs +++ b/src/fuga/mod.rs @@ -149,7 +149,7 @@ #[allow(unused_imports)] pub use crate::macros::{julia_macro::*, matlab_macro::*, r_macro::*}; -pub use peroxide_ad::{ad_function, ad_closure}; +pub use peroxide_ad::{ad_closure, ad_function}; pub use peroxide_num::{ExpLogOps, PowOps, TrigOps}; @@ -157,23 +157,32 @@ pub use crate::traits::{ fp::{FPMatrix, FPVector}, general::Algorithm, math::{InnerProduct, LinearOp, MatrixProduct, Norm, Normed, Vector, VectorProduct}, + matrix::{LinearAlgebra, MatrixTrait}, mutable::{MutFP, MutMatrix}, num::Real, pointer::{MatrixPtr, Oxide, Redox, RedoxCommon}, stable::StableFn, - sugar::{Scalable, ScalableMut, VecOps, ConvToMat}, + sugar::{ConvToMat, Scalable, ScalableMut, VecOps}, }; -#[allow(unused_imports)] -pub use crate::structure::{ - matrix::*, - polynomial::*, - vector::*, - dataframe::*, - ad::*, - //complex::C64, +#[cfg(feature = "parallel")] +pub use crate::traits::{ + fp::{ParallelFPMatrix, ParallelFPVector}, + math::{ + ParallelInnerProduct, ParallelMatrixProduct, ParallelNormed, ParallelVector, + ParallelVectorProduct, + }, + mutable::ParallelMutFP, }; +#[cfg(feature = "complex")] +#[allow(unused_imports)] +pub use crate::complex::{integral::*, matrix::*, vector::*, C64}; + +#[allow(unused_imports)] +#[allow(ambiguous_glob_reexports)] +pub use crate::structure::{ad::*, dataframe::*, matrix::*, polynomial::*, vector::*}; + pub use crate::util::{api::*, low_level::*, non_macro::*, print::*, useful::*, wrapper::*}; #[allow(unused_imports)] @@ -201,33 +210,21 @@ pub use paste; // Enums // ============================================================================= pub use crate::numerical::integral::Integral::{ - GaussLegendre, - NewtonCotes, - G7K15, - G10K21, - G15K31, - G20K41, - G25K51, - G30K61, - G7K15R, - G10K21R, - G15K31R, - G20K41R, - G25K51R, - G30K61R, + GaussLegendre, NewtonCotes, G10K21, G10K21R, G15K31, G15K31R, G20K41, G20K41R, G25K51, G25K51R, + G30K61, G30K61R, G7K15, G7K15R, }; +pub use crate::numerical::spline::SlopeMethod::{Akima, Quadratic}; +pub use crate::statistics::stat::Metric::*; pub use crate::statistics::stat::QType::{ Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, }; -pub use crate::structure::matrix::{ +pub use crate::structure::ad::AD::*; +pub use crate::structure::dataframe::DType::*; +pub use crate::structure::matrix::UPLO::{Lower, Upper}; +pub use crate::traits::matrix::{ Form::{Diagonal, Identity}, SolveKind::{LU, WAZ}, - UPLO::{Upper, Lower} }; -pub use crate::structure::dataframe::DType::*; -pub use crate::structure::ad::AD::*; -pub use crate::numerical::spline::SlopeMethod::{Akima, Quadratic}; -pub use crate::statistics::stat::Metric::*; -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] pub use arrow2::io::parquet::write::CompressionOptions; diff --git a/src/lib.rs b/src/lib.rs index e1929555..4ee752c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,3 +208,6 @@ pub mod util; #[cfg(feature = "complex")] pub mod complex; + +#[cfg(feature = "parallel")] +extern crate rayon; diff --git a/src/numerical/eigen.rs b/src/numerical/eigen.rs index 688764eb..4632ecc9 100644 --- a/src/numerical/eigen.rs +++ b/src/numerical/eigen.rs @@ -4,6 +4,7 @@ pub use self::EigenMethod::*; use crate::structure::matrix::Matrix; +use crate::traits::matrix::MatrixTrait; use crate::util::non_macro::eye_shape; #[derive(Debug, Copy, Clone)] diff --git a/src/numerical/integral.rs b/src/numerical/integral.rs index ac2165dd..beebbb9a 100644 --- a/src/numerical/integral.rs +++ b/src/numerical/integral.rs @@ -1,4 +1,4 @@ -use crate::structure::polynomial::{lagrange_polynomial, Calculus}; +use crate::structure::polynomial::{lagrange_polynomial, Calculus, Polynomial}; use crate::traits::fp::FPVector; use crate::util::non_macro::seq; @@ -132,12 +132,107 @@ impl Integral { } } +/// Type that can be integrated using Newton Cotes Quadrature +pub trait NCIntegrable: std::ops::Sub + Sized { + type NodeY; + type NCPolynomial; + + /// Returns the image of `node_x` under function `f`, in a representation + /// (`Self::NodeY`) suitable for separately computing one Lagrange polynomial + /// for each of the degrees of freedom of `Self` (e.g. a `Vec` with as many + /// entries as the number of degrees of freedom). + fn compute_node_y(f: F, node_x: &[f64]) -> Self::NodeY + where + F: Fn(f64) -> Self; + + /// Computes one [`Lagrange polynomial`](lagrange_polynomial) for each one + /// of the degrees of freedom of `Self`, returning them in a representation + /// (`Self::NCPolynomial`) suitable to separately performing operation on + /// them (e.g. [`Vec`](Polynomial)). + fn compute_polynomial(node_x: &[f64], node_y: &Self::NodeY) -> Self::NCPolynomial; + + /// Separately integrates each of the polynomials obtained using + /// [`compute_polynomial`](NCIntegrable::compute_polynomial). + fn integrate_polynomial(p: &Self::NCPolynomial) -> Self::NCPolynomial; + + /// Separately evaluates each of the polynomial integrated using + /// [`integrate_polynomial`](NCIntegrable::integrate_polynomial) at `x`, + /// then recombines the result into a `Self`. + fn evaluate_polynomial(p: &Self::NCPolynomial, x: f64) -> Self; +} + +impl NCIntegrable for f64 { + type NodeY = Vec; + type NCPolynomial = Polynomial; + + fn compute_node_y(f: F, node_x: &[f64]) -> Vec + where + F: Fn(f64) -> Self, + { + node_x.to_vec().fmap(|x| f(x)) + } + + fn compute_polynomial(node_x: &[f64], node_y: &Vec) -> Polynomial { + lagrange_polynomial(node_x.to_vec(), node_y.to_vec()) + } + + fn integrate_polynomial(p: &Polynomial) -> Polynomial { + p.integral() + } + + fn evaluate_polynomial(p: &Polynomial, x: f64) -> Self { + p.eval(x) + } +} + +/// Type that can be integrated using Gauss Legendre or Kronrod Quadrature +pub trait GLKIntegrable: + std::ops::Add + std::ops::Mul + Sized +{ + /// The neutral element of addition. + const ZERO: Self; +} + +impl GLKIntegrable for f64 { + const ZERO: Self = 0f64; +} + +/// Type that can be integrated using Gauss Kronrod Quadrature +pub trait GKIntegrable: GLKIntegrable + std::ops::Sub + Sized + Clone { + /// Returns `true` if this object is neither infinite nor NaN. + fn is_finite(&self) -> bool; + + /// Returns the value of the norm used by Gauss Kronrod Quadrature to + /// compute relative tolerances. + fn gk_norm(&self) -> f64; + + /// Returns true if this object is less than the tolerance passed as + /// the `tol` argument. By default, returns true if its [`gk_norm`](GKIntegrable::gk_norm) + /// is less than `tol`. + fn is_within_tol(&self, tol: f64) -> bool { + self.gk_norm() < tol + } +} + +impl GKIntegrable for f64 { + fn is_finite(&self) -> bool { + f64::is_finite(*self) + } + + fn gk_norm(&self) -> f64 { + self.abs() + } +} + /// Numerical Integration /// /// # Description -/// `fn integrate(f, (a,b), method) -> f64` +/// `fn integrate(f, (a,b), method) -> Y` +/// +/// `Y` must implement [`GKIntegrable`] and [`NCIntegrable`], like `f64` or +/// `C64` (`complex` feature only). /// -/// * `f`: Target function (`Fn(f64) -> f64`) +/// * `f`: Target function (`Fn(f64) -> Y`) /// * `(a,b)` : Target interval /// * `method` : Numerical integration method /// @@ -159,72 +254,78 @@ impl Integral { /// * `G20K41R` /// * `G25K51R` /// * `G30K61R` -pub fn integrate(f: F, (a, b): (f64, f64), method: Integral) -> f64 +pub fn integrate(f: F, (a, b): (f64, f64), method: Integral) -> Y where - F: Fn(f64) -> f64 + Copy, + F: Fn(f64) -> Y + Copy, + Y: GKIntegrable + NCIntegrable, { match method { Integral::GaussLegendre(n) => gauss_legendre_quadrature(f, n, (a, b)), Integral::NewtonCotes(n) => newton_cotes_quadrature(f, n, (a, b)), - method => gauss_kronrod_quadrature(f, (a,b), method), + method => gauss_kronrod_quadrature(f, (a, b), method), } } /// Newton Cotes Quadrature -pub fn newton_cotes_quadrature(f: F, n: usize, (a, b): (f64, f64)) -> f64 +pub fn newton_cotes_quadrature(f: F, n: usize, (a, b): (f64, f64)) -> Y where - F: Fn(f64) -> f64, + F: Fn(f64) -> Y, + Y: NCIntegrable, { let h = (b - a) / (n as f64); let node_x = seq(a, b, h); - let node_y = node_x.fmap(|x| f(x)); - let p = lagrange_polynomial(node_x, node_y); - let q = p.integral(); + let node_y = Y::compute_node_y(f, &node_x); + let p = Y::compute_polynomial(&node_x, &node_y); + let q = Y::integrate_polynomial(&p); - q.eval(b) - q.eval(a) + Y::evaluate_polynomial(&q, b) - Y::evaluate_polynomial(&q, a) } /// Gauss Legendre Quadrature /// /// # Type -/// * `f, n, (a,b) -> f64` -/// * `f`: Numerical function (`Fn(f64) -> f64`) +/// * `f, n, (a,b) -> Y` +/// * `Y` implements [`GLKIntegrable`], like `f64` or `C64` (`complex` feature +/// only) +/// * `f`: Numerical function (`Fn(f64) -> Y`) /// * `n`: Order of Legendre polynomial (up to 16) /// * `(a,b)`: Interval of integration /// /// # Reference /// * A. N. Lowan et al. (1942) -/// * [Keisan Online Calculator](https://keisan.casio.com/exec/system/1329114617) -pub fn gauss_legendre_quadrature(f: F, n: usize, (a, b): (f64, f64)) -> f64 +pub fn gauss_legendre_quadrature(f: F, n: usize, (a, b): (f64, f64)) -> Y where - F: Fn(f64) -> f64, + F: Fn(f64) -> Y, + Y: GLKIntegrable, { - (b - a) / 2f64 * unit_gauss_legendre_quadrature(|x| f(x * (b - a) / 2f64 + (a + b) / 2f64), n) + unit_gauss_legendre_quadrature(|x| f(x * (b - a) / 2f64 + (a + b) / 2f64), n) * ((b - a) / 2f64) } /// Gauss Kronrod Quadrature /// /// # Type -/// * `f, (a,b), method -> f64` -/// * `f`: Numerical function (`Fn(f64) -> f64 + Copy`) +/// * `f, (a,b), method -> Y` +/// * `Y` implements [`GKIntegrable`], like `f64` or `C64` (`complex` feature +/// only) +/// * `f`: Numerical function (`Fn(f64) -> Y + Copy`) /// * `(a,b)`: Interval of integration /// * `method`: Integration method /// * `G7K15(tol)` /// /// # Reference /// * arXiv: [1003.4629](https://arxiv.org/abs/1003.4629) -/// * [Keisan Online Calculator](https://keisan.casio.com/exec/system/1329114617) #[allow(non_snake_case)] -pub fn gauss_kronrod_quadrature(f: F, (a, b): (T, S), method: Integral) -> f64 +pub fn gauss_kronrod_quadrature(f: F, (a, b): (T, S), method: Integral) -> Y where - F: Fn(f64) -> f64 + Copy, - T: Into, - S: Into, + F: Fn(f64) -> Y + Copy, + T: Into, + S: Into, + Y: GKIntegrable, { let (g, k) = method.get_gauss_kronrod_order(); let tol = method.get_tol(); let max_iter = method.get_max_iter(); - let mut I = 0f64; + let mut I = Y::ZERO; let mut S: Vec<(f64, f64, f64, u32)> = vec![]; S.push((a.into(), b.into(), tol, max_iter)); @@ -235,15 +336,15 @@ where let K = kronrod_quadrature(f, k as usize, (a, b)); let c = (a + b) / 2f64; let tol_curr = if method.is_relative() { - tol * G + tol * G.gk_norm() } else { tol }; - if (G - K).abs() < tol_curr || a == b || max_iter == 0 { - if ! G.is_finite() { + if (G.clone() - K).is_within_tol(tol_curr) || a == b || max_iter == 0 { + if !G.is_finite() { return G; } - I += G; + I = I + G; } else { S.push((a, c, tol / 2f64, max_iter - 1)); S.push((c, b, tol / 2f64, max_iter - 1)); @@ -255,24 +356,26 @@ where I } -pub fn kronrod_quadrature(f: F, n: usize, (a, b): (f64, f64)) -> f64 +pub fn kronrod_quadrature(f: F, n: usize, (a, b): (f64, f64)) -> Y where - F: Fn(f64) -> f64, + F: Fn(f64) -> Y, + Y: GLKIntegrable, { - (b - a) / 2f64 * unit_kronrod_quadrature(|x| f(x * (b-a) / 2f64 + (a + b) / 2f64), n) + unit_kronrod_quadrature(|x| f(x * (b - a) / 2f64 + (a + b) / 2f64), n) * ((b - a) / 2f64) } // ============================================================================= // Gauss Legendre Backends // ============================================================================= -fn unit_gauss_legendre_quadrature(f: F, n: usize) -> f64 +fn unit_gauss_legendre_quadrature(f: F, n: usize) -> Y where - F: Fn(f64) -> f64, + F: Fn(f64) -> Y, + Y: GLKIntegrable, { let (a, x) = gauss_legendre_table(n); - let mut s = 0f64; + let mut s = Y::ZERO; for i in 0..a.len() { - s += a[i] * f(x[i]); + s = s + f(x[i]) * a[i]; } s } @@ -378,14 +481,15 @@ fn gauss_legendre_table(n: usize) -> (Vec, Vec) { // Gauss Kronrod Backends // ============================================================================= -fn unit_kronrod_quadrature(f: F, n: usize) -> f64 +fn unit_kronrod_quadrature(f: F, n: usize) -> Y where - F: Fn(f64) -> f64, + F: Fn(f64) -> Y, + Y: GLKIntegrable, { - let (a,x) = kronrod_table(n); - let mut s = 0f64; - for i in 0 .. a.len() { - s += a[i] * f(x[i]); + let (a, x) = kronrod_table(n); + let mut s = Y::ZERO; + for i in 0..a.len() { + s = s + f(x[i]) * a[i]; } s } @@ -414,28 +518,28 @@ fn kronrod_table(n: usize) -> (Vec, Vec) { match n % 2 { 0 => { - for i in 0 .. ref_node.len() { + for i in 0..ref_node.len() { result_node[i] = ref_node[i]; result_weight[i] = ref_weight[i]; } - for i in ref_node.len() .. n { - result_node[i] = -ref_node[n-i-1]; - result_weight[i] = ref_weight[n-i-1]; + for i in ref_node.len()..n { + result_node[i] = -ref_node[n - i - 1]; + result_weight[i] = ref_weight[n - i - 1]; } } 1 => { - for i in 0 .. ref_node.len() { + for i in 0..ref_node.len() { result_node[i] = ref_node[i]; result_weight[i] = ref_weight[i]; } - for i in ref_node.len() .. n { - result_node[i] = -ref_node[n-i]; - result_weight[i] = ref_weight[n-i]; + for i in ref_node.len()..n { + result_node[i] = -ref_node[n - i]; + result_weight[i] = ref_weight[n - i]; } } - _ => unreachable!() + _ => unreachable!(), } (result_weight, result_node) } @@ -922,7 +1026,7 @@ const LEGENDRE_WEIGHT_25: [f64; 13] = [ 0.054904695975835192, 0.040939156701306313, 0.026354986615032137, - 0.011393798501026288, + 0.011393798501026288, ]; const LEGENDRE_WEIGHT_26: [f64; 13] = [ 0.118321415279262277, diff --git a/src/numerical/mod.rs b/src/numerical/mod.rs index e228e556..0a21bfc3 100644 --- a/src/numerical/mod.rs +++ b/src/numerical/mod.rs @@ -8,4 +8,4 @@ pub mod ode; pub mod optimize; pub mod root; pub mod spline; -pub mod utils; \ No newline at end of file +pub mod utils; diff --git a/src/numerical/newton.rs b/src/numerical/newton.rs index f05998bc..e3ee8f2b 100644 --- a/src/numerical/newton.rs +++ b/src/numerical/newton.rs @@ -1,14 +1,13 @@ use crate::numerical::utils::jacobian; -use crate::structure::matrix::*; use crate::structure::ad::*; use crate::traits::{ math::{Norm, Normed, Vector}, + matrix::LinearAlgebra, mutable::MutFP, }; /// Newton-Raphson Method -pub fn newton) -> Vec + Copy>(init_cond: Vec, f: F, rtol: f64) -> Vec -{ +pub fn newton) -> Vec + Copy>(init_cond: Vec, f: F, rtol: f64) -> Vec { let mut x_next = init_cond; let mut x = x_next.clone(); update(&mut x_next, f); @@ -23,8 +22,7 @@ pub fn newton) -> Vec + Copy>(init_cond: Vec, f: F, rtol x_next } -fn update) -> Vec + Copy>(xs: &mut Vec, f: F) -{ +fn update) -> Vec + Copy>(xs: &mut Vec, f: F) { let j = jacobian(f, &xs); let pinv_j = j.pseudo_inv(); //let fx = f(NumberVector::from_f64_vec(xs.clone())).to_f64_vec(); diff --git a/src/numerical/ode.rs b/src/numerical/ode.rs index 73b89f85..4025bbe5 100644 --- a/src/numerical/ode.rs +++ b/src/numerical/ode.rs @@ -87,7 +87,7 @@ //! } //! ``` -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; /// Trait for defining an ODE problem. /// @@ -117,7 +117,6 @@ pub trait ODEProblem { fn rhs(&self, t: f64, y: &[f64], dy: &mut [f64]) -> Result<()>; } - /// Trait for ODE integrators. /// /// Implement this trait to define your own ODE integrator. @@ -125,7 +124,6 @@ pub trait ODEIntegrator { fn step(&self, problem: &P, t: f64, y: &mut [f64], dt: f64) -> Result; } - /// Enum for ODE errors. /// /// # Variants @@ -165,7 +163,11 @@ pub enum ODEError { impl std::fmt::Display for ODEError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ODEError::ConstraintViolation(t, y, dy) => write!(f, "Constraint violation at t = {}, y = {:?}, dy = {:?}", t, y, dy), + ODEError::ConstraintViolation(t, y, dy) => write!( + f, + "Constraint violation at t = {}, y = {:?}, dy = {:?}", + t, y, dy + ), ODEError::ReachedMaxStepIter => write!(f, "Reached maximum number of steps per step"), } } @@ -175,7 +177,12 @@ impl std::fmt::Display for ODEError { /// /// Implement this trait to define your own ODE solver. pub trait ODESolver { - fn solve(&self, problem: &P, t_span: (f64, f64), dt: f64) -> Result<(Vec, Vec>)>; + fn solve( + &self, + problem: &P, + t_span: (f64, f64), + dt: f64, + ) -> Result<(Vec, Vec>)>; } /// A basic ODE solver using a specified integrator. @@ -222,7 +229,12 @@ impl BasicODESolver { } impl ODESolver for BasicODESolver { - fn solve(&self, problem: &P, t_span: (f64, f64), dt: f64) -> Result<(Vec, Vec>)> { + fn solve( + &self, + problem: &P, + t_span: (f64, f64), + dt: f64, + ) -> Result<(Vec, Vec>)> { let mut t = t_span.0; let mut dt = dt; let mut y = problem.initial_conditions(); @@ -248,7 +260,7 @@ impl ODESolver for BasicODESolver { /// /// ```text /// C | A -/// - - - +/// - - - /// | BU (Coefficient for update) /// | BE (Coefficient for estimate error) /// ``` @@ -295,10 +307,10 @@ impl ODEIntegrator for BU { let mut k_vec = vec![vec![0.0; n]; n_k]; let mut y_temp = y.to_vec(); - for i in 0 .. n_k { - for i in 0 .. n { + for i in 0..n_k { + for i in 0..n { let mut s = 0.0; - for j in 0 .. i { + for j in 0..i { s += Self::A[i][j] * k_vec[j][i]; } y_temp[i] = y[i] + dt * s; @@ -308,9 +320,9 @@ impl ODEIntegrator for BU { if !Self::BE.is_empty() { let mut error = 0f64; - for i in 0 .. n { + for i in 0..n { let mut s = 0.0; - for j in 0 .. n_k { + for j in 0..n_k { s += (Self::BU[j] - Self::BE[j]) * k_vec[j][i]; } error = error.max(dt * s.abs()) @@ -318,12 +330,12 @@ impl ODEIntegrator for BU { let factor = (self.tol() * dt / error).powf(0.2); let new_dt = self.safety_factor() * dt * factor; - let new_dt = new_dt.clamp(self.min_step_size(),self.max_step_size()); + let new_dt = new_dt.clamp(self.min_step_size(), self.max_step_size()); if error < self.tol() { - for i in 0 .. n { + for i in 0..n { let mut s = 0.0; - for j in 0 .. n_k { + for j in 0..n_k { s += Self::BU[j] * k_vec[j][i]; } y[i] += dt * s; @@ -337,9 +349,9 @@ impl ODEIntegrator for BU { dt = new_dt; } } else { - for i in 0 .. n { + for i in 0..n { let mut s = 0.0; - for j in 0 .. n_k { + for j in 0..n_k { s += Self::BU[j] * k_vec[j][i]; } y[i] += dt * s; @@ -363,11 +375,7 @@ pub struct RALS3; impl ButcherTableau for RALS3 { const C: &'static [f64] = &[0.0, 0.5, 0.75]; - const A: &'static [&'static [f64]] = &[ - &[], - &[0.5], - &[0.0, 0.75], - ]; + const A: &'static [&'static [f64]] = &[&[], &[0.5], &[0.0, 0.75]]; const BU: &'static [f64] = &[2.0 / 9.0, 1.0 / 3.0, 4.0 / 9.0]; const BE: &'static [f64] = &[]; } @@ -420,11 +428,37 @@ impl ButcherTableau for RK5 { &[0.2], &[0.075, 0.225], &[44.0 / 45.0, -56.0 / 15.0, 32.0 / 9.0], - &[19372.0 / 6561.0, -25360.0 / 2187.0, 64448.0 / 6561.0, -212.0 / 729.0], - &[9017.0 / 3168.0, -355.0 / 33.0, 46732.0 / 5247.0, 49.0 / 176.0, -5103.0 / 18656.0], - &[35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0, 11.0 / 84.0], + &[ + 19372.0 / 6561.0, + -25360.0 / 2187.0, + 64448.0 / 6561.0, + -212.0 / 729.0, + ], + &[ + 9017.0 / 3168.0, + -355.0 / 33.0, + 46732.0 / 5247.0, + 49.0 / 176.0, + -5103.0 / 18656.0, + ], + &[ + 35.0 / 384.0, + 0.0, + 500.0 / 1113.0, + 125.0 / 192.0, + -2187.0 / 6784.0, + 11.0 / 84.0, + ], + ]; + const BU: &'static [f64] = &[ + 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, ]; - const BU: &'static [f64] = &[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]; const BE: &'static [f64] = &[]; } @@ -454,13 +488,31 @@ pub struct BS23 { impl Default for BS23 { fn default() -> Self { - Self { tol: 1e-3, safety_factor: 0.9, min_step_size: 1e-6, max_step_size: 1e-1, max_step_iter: 100 } + Self { + tol: 1e-3, + safety_factor: 0.9, + min_step_size: 1e-6, + max_step_size: 1e-1, + max_step_iter: 100, + } } } impl BS23 { - pub fn new(tol: f64, safety_factor: f64, min_step_size: f64, max_step_size: f64, max_step_iter: usize) -> Self { - Self { tol, safety_factor, min_step_size, max_step_size, max_step_iter } + pub fn new( + tol: f64, + safety_factor: f64, + min_step_size: f64, + max_step_size: f64, + max_step_iter: usize, + ) -> Self { + Self { + tol, + safety_factor, + min_step_size, + max_step_size, + max_step_iter, + } } } @@ -475,14 +527,23 @@ impl ButcherTableau for BS23 { const BU: &'static [f64] = &[2.0 / 9.0, 1.0 / 3.0, 4.0 / 9.0, 0.0]; const BE: &'static [f64] = &[7.0 / 24.0, 0.25, 1.0 / 3.0, 0.125]; - fn tol(&self) -> f64 { self.tol } - fn safety_factor(&self) -> f64 { self.safety_factor } - fn min_step_size(&self) -> f64 { self.min_step_size } - fn max_step_size(&self) -> f64 { self.max_step_size } - fn max_step_iter(&self) -> usize { self.max_step_iter } + fn tol(&self) -> f64 { + self.tol + } + fn safety_factor(&self) -> f64 { + self.safety_factor + } + fn min_step_size(&self) -> f64 { + self.min_step_size + } + fn max_step_size(&self) -> f64 { + self.max_step_size + } + fn max_step_iter(&self) -> usize { + self.max_step_iter + } } - /// Runge-Kutta-Fehlberg 4/5th order integrator. /// /// This integrator uses the Runge-Kutta-Fehlberg method, which is an adaptive step size integrator. @@ -519,7 +580,13 @@ impl Default for RKF45 { } impl RKF45 { - pub fn new(tol: f64, safety_factor: f64, min_step_size: f64, max_step_size: f64, max_step_iter: usize) -> Self { + pub fn new( + tol: f64, + safety_factor: f64, + min_step_size: f64, + max_step_size: f64, + max_step_iter: usize, + ) -> Self { Self { tol, safety_factor, @@ -538,16 +605,46 @@ impl ButcherTableau for RKF45 { &[3.0 / 32.0, 9.0 / 32.0], &[1932.0 / 2197.0, -7200.0 / 2197.0, 7296.0 / 2197.0], &[439.0 / 216.0, -8.0, 3680.0 / 513.0, -845.0 / 4104.0], - &[-8.0 / 27.0, 2.0, -3544.0 / 2565.0, 1859.0 / 4104.0, -11.0 / 40.0], + &[ + -8.0 / 27.0, + 2.0, + -3544.0 / 2565.0, + 1859.0 / 4104.0, + -11.0 / 40.0, + ], + ]; + const BU: &'static [f64] = &[ + 16.0 / 135.0, + 0.0, + 6656.0 / 12825.0, + 28561.0 / 56430.0, + -9.0 / 50.0, + 2.0 / 55.0, + ]; + const BE: &'static [f64] = &[ + 25.0 / 216.0, + 0.0, + 1408.0 / 2565.0, + 2197.0 / 4104.0, + -1.0 / 5.0, + 0.0, ]; - const BU: &'static [f64] = &[16.0 / 135.0, 0.0, 6656.0 / 12825.0, 28561.0 / 56430.0, -9.0 / 50.0, 2.0 / 55.0]; - const BE: &'static [f64] = &[25.0 / 216.0, 0.0, 1408.0 / 2565.0, 2197.0 / 4104.0, -1.0 / 5.0, 0.0]; - fn tol(&self) -> f64 { self.tol } - fn safety_factor(&self) -> f64 { self.safety_factor } - fn min_step_size(&self) -> f64 { self.min_step_size } - fn max_step_size(&self) -> f64 { self.max_step_size } - fn max_step_iter(&self) -> usize { self.max_step_iter } + fn tol(&self) -> f64 { + self.tol + } + fn safety_factor(&self) -> f64 { + self.safety_factor + } + fn min_step_size(&self) -> f64 { + self.min_step_size + } + fn max_step_size(&self) -> f64 { + self.max_step_size + } + fn max_step_iter(&self) -> usize { + self.max_step_iter + } } /// Dormand-Prince 5(4) method @@ -585,7 +682,13 @@ impl Default for DP45 { } impl DP45 { - pub fn new(tol: f64, safety_factor: f64, min_step_size: f64, max_step_size: f64, max_step_iter: usize) -> Self { + pub fn new( + tol: f64, + safety_factor: f64, + min_step_size: f64, + max_step_size: f64, + max_step_iter: usize, + ) -> Self { Self { tol, safety_factor, @@ -603,18 +706,62 @@ impl ButcherTableau for DP45 { &[0.2], &[0.075, 0.225], &[44.0 / 45.0, -56.0 / 15.0, 32.0 / 9.0], - &[19372.0 / 6561.0, -25360.0 / 2187.0, 64448.0 / 6561.0, -212.0 / 729.0], - &[9017.0 / 3168.0, -355.0 / 33.0, 46732.0 / 5247.0, 49.0 / 176.0, -5103.0 / 18656.0], - &[35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0, 11.0 / 84.0], + &[ + 19372.0 / 6561.0, + -25360.0 / 2187.0, + 64448.0 / 6561.0, + -212.0 / 729.0, + ], + &[ + 9017.0 / 3168.0, + -355.0 / 33.0, + 46732.0 / 5247.0, + 49.0 / 176.0, + -5103.0 / 18656.0, + ], + &[ + 35.0 / 384.0, + 0.0, + 500.0 / 1113.0, + 125.0 / 192.0, + -2187.0 / 6784.0, + 11.0 / 84.0, + ], + ]; + const BU: &'static [f64] = &[ + 35.0 / 384.0, + 0.0, + 500.0 / 1113.0, + 125.0 / 192.0, + -2187.0 / 6784.0, + 11.0 / 84.0, + 0.0, + ]; + const BE: &'static [f64] = &[ + 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, ]; - const BU: &'static [f64] = &[35.0 / 384.0, 0.0, 500.0 / 1113.0, 125.0 / 192.0, -2187.0 / 6784.0, 11.0 / 84.0, 0.0]; - const BE: &'static [f64] = &[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]; - fn tol(&self) -> f64 { self.tol } - fn safety_factor(&self) -> f64 { self.safety_factor } - fn min_step_size(&self) -> f64 { self.min_step_size } - fn max_step_size(&self) -> f64 { self.max_step_size } - fn max_step_iter(&self) -> usize { self.max_step_iter } + fn tol(&self) -> f64 { + self.tol + } + fn safety_factor(&self) -> f64 { + self.safety_factor + } + fn min_step_size(&self) -> f64 { + self.min_step_size + } + fn max_step_size(&self) -> f64 { + self.max_step_size + } + fn max_step_iter(&self) -> usize { + self.max_step_iter + } } /// Tsitouras 5(4) method @@ -656,7 +803,13 @@ impl Default for TSIT45 { } impl TSIT45 { - pub fn new(tol: f64, safety_factor: f64, min_step_size: f64, max_step_size: f64, max_step_iter: usize) -> Self { + pub fn new( + tol: f64, + safety_factor: f64, + min_step_size: f64, + max_step_size: f64, + max_step_iter: usize, + ) -> Self { Self { tol, safety_factor, @@ -673,27 +826,70 @@ impl ButcherTableau for TSIT45 { &[], &[Self::C[1]], &[Self::C[2] - 0.335480655492357, 0.335480655492357], - &[Self::C[3] - (-6.359448489975075 + 4.362295432869581), -6.359448489975075, 4.362295432869581], - &[Self::C[4] - (-11.74888356406283 + 7.495539342889836 - 0.09249506636175525), -11.74888356406283, 7.495539342889836, -0.09249506636175525], - &[Self::C[5] - (-12.92096931784711 + 8.159367898576159 - 0.0715849732814010 - 0.02826905039406838), -12.92096931784711, 8.159367898576159, -0.0715849732814010, -0.02826905039406838], - &[Self::BU[0], Self::BU[1], Self::BU[2], Self::BU[3], Self::BU[4], Self::BU[5]], + &[ + Self::C[3] - (-6.359448489975075 + 4.362295432869581), + -6.359448489975075, + 4.362295432869581, + ], + &[ + Self::C[4] - (-11.74888356406283 + 7.495539342889836 - 0.09249506636175525), + -11.74888356406283, + 7.495539342889836, + -0.09249506636175525, + ], + &[ + Self::C[5] + - (-12.92096931784711 + 8.159367898576159 + - 0.0715849732814010 + - 0.02826905039406838), + -12.92096931784711, + 8.159367898576159, + -0.0715849732814010, + -0.02826905039406838, + ], + &[ + Self::BU[0], + Self::BU[1], + Self::BU[2], + Self::BU[3], + Self::BU[4], + Self::BU[5], + ], + ]; + const BU: &'static [f64] = &[ + 0.09646076681806523, + 0.01, + 0.4798896504144996, + 1.379008574103742, + -3.290069515436081, + 2.324710524099774, + 0.0, ]; - const BU: &'static [f64] = &[0.09646076681806523, 0.01, 0.4798896504144996, 1.379008574103742, -3.290069515436081, 2.324710524099774, 0.0]; const BE: &'static [f64] = &[ 0.001780011052226, 0.000816434459657, - - 0.007880878010262, + -0.007880878010262, 0.144711007173263, - - 0.582357165452555, + -0.582357165452555, 0.458082105929187, 1.0 / 66.0, ]; - fn tol(&self) -> f64 { self.tol } - fn safety_factor(&self) -> f64 { self.safety_factor } - fn min_step_size(&self) -> f64 { self.min_step_size } - fn max_step_size(&self) -> f64 { self.max_step_size } - fn max_step_iter(&self) -> usize { self.max_step_iter } + fn tol(&self) -> f64 { + self.tol + } + fn safety_factor(&self) -> f64 { + self.safety_factor + } + fn min_step_size(&self) -> f64 { + self.min_step_size + } + fn max_step_size(&self) -> f64 { + self.max_step_size + } + fn max_step_iter(&self) -> usize { + self.max_step_iter + } } // ┌─────────────────────────────────────────────────────────┐ @@ -776,8 +972,19 @@ impl ODEIntegrator for GL4 { let mut max_diff = 0f64; for i in 0..n { - max_diff = max_diff.max((y1[i] - y[i] - dt * (c * k1[i] + d * k2[i] - sqrt3 * (k2[i] - k1[i]) / 2.0)).abs()) - .max((y2[i] - y[i] - dt * (c * k1[i] + d * k2[i] + sqrt3 * (k2[i] - k1[i]) / 2.0)).abs()); + max_diff = max_diff + .max( + (y1[i] + - y[i] + - dt * (c * k1[i] + d * k2[i] - sqrt3 * (k2[i] - k1[i]) / 2.0)) + .abs(), + ) + .max( + (y2[i] + - y[i] + - dt * (c * k1[i] + d * k2[i] + sqrt3 * (k2[i] - k1[i]) / 2.0)) + .abs(), + ); } if max_diff < self.tol { diff --git a/src/numerical/optimize.rs b/src/numerical/optimize.rs index b894e28b..dcb60005 100644 --- a/src/numerical/optimize.rs +++ b/src/numerical/optimize.rs @@ -111,8 +111,9 @@ pub use self::OptMethod::{GaussNewton, GradientDescent, LevenbergMarquardt}; use self::OptOption::{InitParam, MaxIter}; use crate::numerical::utils::jacobian; -use crate::structure::matrix::{LinearAlgebra, Matrix}; -use crate::structure::ad::{AD, ADVec}; +use crate::structure::ad::{ADVec, AD}; +use crate::structure::matrix::Matrix; +use crate::traits::matrix::{LinearAlgebra, MatrixTrait}; use crate::util::useful::max; use std::collections::HashMap; @@ -224,13 +225,15 @@ where /// Set initial lambda for `LevenbergMarquardt` pub fn set_lambda_init(&mut self, lambda_init: f64) -> &mut Self { - self.hyperparams.insert("lambda_init".to_string(), lambda_init); + self.hyperparams + .insert("lambda_init".to_string(), lambda_init); self } /// Set maximum lambda for `LevenbergMarquardt` pub fn set_lambda_max(&mut self, lambda_max: f64) -> &mut Self { - self.hyperparams.insert("lambda_max".to_string(), lambda_max); + self.hyperparams + .insert("lambda_max".to_string(), lambda_max); self } @@ -287,13 +290,20 @@ where let mut chi2 = ((&y - &y_hat).t() * (&y - &y_hat))[(0, 0)]; let mut nu = 2f64; let lambda_0 = *self.hyperparams.get("lambda_init").unwrap_or(&1e-3); - let lambda_max = *self.hyperparams.get("lambda_max").unwrap_or(&f64::MAX.sqrt()); + let lambda_max = *self + .hyperparams + .get("lambda_max") + .unwrap_or(&f64::MAX.sqrt()); let mut lambda = lambda_0 * max(jtj.diag()); for i in 0..max_iter { if lambda > lambda_max { - println!("Caution: At {}-th iter, lambda exceeds max value: {}", i+1, lambda); + println!( + "Caution: At {}-th iter, lambda exceeds max value: {}", + i + 1, + lambda + ); break; } diff --git a/src/numerical/root.rs b/src/numerical/root.rs index 17b67ac7..050076e0 100644 --- a/src/numerical/root.rs +++ b/src/numerical/root.rs @@ -66,26 +66,26 @@ //! extern crate peroxide; //! use peroxide::fuga::*; //! use anyhow::Result; -//! +//! //! fn main() -> Result<()> { //! let root_bisect = bisection!(f, (0.0, 2.0), 100, 1e-6)?; //! let root_newton = newton!(f, 0.0, 100, 1e-6)?; //! let root_false_pos = false_position!(f, (0.0, 2.0), 100, 1e-6)?; //! let root_secant = secant!(f, (0.0, 2.0), 100, 1e-6)?; -//! +//! //! println!("root_bisect: {}", root_bisect); //! println!("root_newton: {}", root_newton); //! println!("root_false_pos: {}", root_false_pos); //! println!("root_secant: {}", root_secant); -//! +//! //! assert!(f(root_bisect).abs() < 1e-6); //! assert!(f(root_newton).abs() < 1e-6); //! assert!(f(root_false_pos).abs() < 1e-6); //! assert!(f(root_secant).abs() < 1e-6); -//! +//! //! Ok(()) //! } -//! +//! //! #[ad_function] //! fn f(x: f64) -> f64 { //! (x - 1f64).powi(3) @@ -212,9 +212,9 @@ //! The `Cosine` struct implements the `RootFindingProblem` trait for the `f64` initial guess type. //! The initial guess is set to `0.0`, which is a point where the derivative of the cosine function is 0. //! This leads to the `NewtonMethod` returning a `RootError::ZeroDerivative` error, which is handled in the example. -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; -use crate::traits::math::{Normed, Norm, LinearOp}; +use crate::traits::math::{LinearOp, Norm, Normed}; use crate::traits::sugar::{ConvToMat, VecOps}; use crate::util::non_macro::zeros; @@ -247,12 +247,15 @@ macro_rules! bisection { } let problem = BisectionProblem { f: $f }; - let bisection = BisectionMethod { max_iter: $max_iter, tol: $tol }; + let bisection = BisectionMethod { + max_iter: $max_iter, + tol: $tol, + }; match bisection.find(&problem) { Ok(root) => Ok(root[0]), Err(e) => Err(e), } - }} + }}; } /// High level macro for newton (using Automatic differentiation) @@ -284,7 +287,7 @@ macro_rules! newton { impl RootFindingProblem<1, 1, f64> for NewtonProblem { fn initial_guess(&self) -> f64 { - $x + $x } fn function(&self, x: [f64; 1]) -> Result<[f64; 1]> { @@ -300,12 +303,15 @@ macro_rules! newton { } let problem = NewtonProblem; - let newton = NewtonMethod { max_iter: $max_iter, tol: $tol }; + let newton = NewtonMethod { + max_iter: $max_iter, + tol: $tol, + }; match newton.find(&problem) { Ok(root) => Ok(root[0]), Err(e) => Err(e), } - }} + }}; } /// High level macro for false position @@ -334,12 +340,15 @@ macro_rules! false_position { } let problem = FalsePositionProblem { f: $f }; - let false_position = FalsePositionMethod { max_iter: $max_iter, tol: $tol }; + let false_position = FalsePositionMethod { + max_iter: $max_iter, + tol: $tol, + }; match false_position.find(&problem) { Ok(root) => Ok(root[0]), Err(e) => Err(e), } - }} + }}; } /// High level macro for secant @@ -368,15 +377,17 @@ macro_rules! secant { } let problem = SecantProblem { f: $f }; - let secant = SecantMethod { max_iter: $max_iter, tol: $tol }; + let secant = SecantMethod { + max_iter: $max_iter, + tol: $tol, + }; match secant.find(&problem) { Ok(root) => Ok(root[0]), Err(e) => Err(e), } - }} + }}; } - // ┌─────────────────────────────────────────────────────────┐ // Type aliases // └─────────────────────────────────────────────────────────┘ @@ -459,7 +470,7 @@ impl std::fmt::Display for RootError { RootError::NoRoot => write!(f, "There is no root in the interval"), RootError::NotConverge(a) => write!(f, "Not yet converge. Our guess is {:?}", a), RootError::ZeroDerivative(a) => write!(f, "Zero derivative in {:?}", a), - RootError::ZeroSecant(a,b) => write!(f, "Zero secant in ({:?}, {:?})", a, b), + RootError::ZeroSecant(a, b) => write!(f, "Zero secant in ({:?}, {:?})", a, b), } } } @@ -483,7 +494,7 @@ impl std::fmt::Display for RootError { macro_rules! single_function { ($problem:expr, $x:expr) => {{ $problem.function([$x])?[0] - }} + }}; } /// Macro for single derivative @@ -505,7 +516,7 @@ macro_rules! single_function { macro_rules! single_derivative { ($problem:expr, $x:expr) => {{ $problem.derivative([$x])?[0][0] - }} + }}; } // ┌─────────────────────────────────────────────────────────┐ @@ -542,10 +553,7 @@ impl RootFinder<1, 1, (f64, f64)> for BisectionMethod { self.tol } - fn find>( - &self, - problem: &P, - ) -> Result<[f64; 1]> { + fn find>(&self, problem: &P) -> Result<[f64; 1]> { let state = problem.initial_guess(); let (mut a, mut b) = state; let mut fa = single_function!(problem, a); @@ -613,10 +621,7 @@ impl RootFinder<1, 1, f64> for NewtonMethod { fn tol(&self) -> f64 { self.tol } - fn find>( - &self, - problem: &P, - ) -> Result<[f64; 1]> { + fn find>(&self, problem: &P) -> Result<[f64; 1]> { let mut x = problem.initial_guess(); for _ in 0..self.max_iter { @@ -667,10 +672,7 @@ impl RootFinder<1, 1, (f64, f64)> for SecantMethod { fn tol(&self) -> f64 { self.tol } - fn find>( - &self, - problem: &P, - ) -> Result<[f64; 1]> { + fn find>(&self, problem: &P) -> Result<[f64; 1]> { let state = problem.initial_guess(); let (mut x0, mut x1) = state; let mut f0 = single_function!(problem, x0); @@ -729,10 +731,7 @@ impl RootFinder<1, 1, (f64, f64)> for FalsePositionMethod { fn tol(&self) -> f64 { self.tol } - fn find>( - &self, - problem: &P, - ) -> Result<[f64; 1]> { + fn find>(&self, problem: &P) -> Result<[f64; 1]> { let state = problem.initial_guess(); let (mut a, mut b) = state; let mut fa = single_function!(problem, a); @@ -792,7 +791,7 @@ impl RootFinder<1, 1, (f64, f64)> for FalsePositionMethod { /// ```rust /// use peroxide::fuga::*; /// use peroxide::numerical::root::{Pt, Intv}; -/// +/// /// fn main() -> Result<(), Box> { /// let problem = CircleTangentLine; /// let broyden = BroydenMethod { max_iter: 100, tol: 1e-6, rtol: 1e-6 }; @@ -805,9 +804,9 @@ impl RootFinder<1, 1, (f64, f64)> for FalsePositionMethod { /// /// Ok(()) /// } -/// +/// /// struct CircleTangentLine; -/// +/// /// impl RootFindingProblem<2, 2, Intv<2>> for CircleTangentLine { /// fn function(&self, x: Pt<2>) -> anyhow::Result> { /// Ok([ @@ -815,7 +814,7 @@ impl RootFinder<1, 1, (f64, f64)> for FalsePositionMethod { /// x[0] + x[1] - 2f64.sqrt() /// ]) /// } -/// +/// /// fn initial_guess(&self) -> Intv<2> { /// ([0.0, 0.1], [-0.1, 0.2]) /// } @@ -836,10 +835,7 @@ impl RootFinder> for BroydenMethod fn tol(&self) -> f64 { self.tol } - fn find>>( - &self, - problem: &P, - ) -> Result> { + fn find>>(&self, problem: &P) -> Result> { // Init state let state = problem.initial_guess(); let (mut x0, mut x1) = state; @@ -857,11 +853,19 @@ impl RootFinder> for BroydenMethod if fx1.norm(Norm::L2) < self.tol { return Ok(x1); } - let dx = x1.iter().zip(x0.iter()).map(|(x1, x0)| x1 - x0).collect::>(); + let dx = x1 + .iter() + .zip(x0.iter()) + .map(|(x1, x0)| x1 - x0) + .collect::>(); if dx.norm(Norm::L2) < self.rtol { return Ok(x1); } - let df = fx1.iter().zip(fx0.iter()).map(|(fx1, fx0)| fx1 - fx0).collect::>(); + let df = fx1 + .iter() + .zip(fx0.iter()) + .map(|(fx1, fx0)| fx1 - fx0) + .collect::>(); let denom = dx.add_v(&H.apply(&df)); let right = &dx.to_row() * &H; diff --git a/src/numerical/spline.rs b/src/numerical/spline.rs index c1da0df1..4978769d 100644 --- a/src/numerical/spline.rs +++ b/src/numerical/spline.rs @@ -96,7 +96,7 @@ //! //! ```rust //! use peroxide::fuga::*; -//! +//! //! fn main() -> Result<(), Box> { //! let knots = vec![0f64, 1f64, 2f64, 3f64]; //! let degree = 3; @@ -108,17 +108,17 @@ //! vec![0.8, 1f64], //! vec![1f64, 2f64], //! ]; -//! +//! //! //! let spline = BSpline::clamped(degree, knots, control_points.clone())?; //! let t = linspace(0f64, 3f64, 200); //! let (x, y): (Vec, Vec) = spline.eval_vec(&t).into_iter().unzip(); -//! +//! //! # #[cfg(feature = "plot")] //! # { //! let control_x = control_points.iter().map(|v| v[0]).collect::>(); //! let control_y = control_points.iter().map(|v| v[1]).collect::>(); -//! +//! //! let mut plt = Plot2D::new(); //! plt //! .insert_pair((x.clone(), y.clone())) @@ -132,13 +132,13 @@ //! .set_path("example_data/b_spline_test.png") //! .savefig()?; //! # } -//! +//! //! let mut df = DataFrame::new(vec![]); //! df.push("t", Series::new(t)); //! df.push("x", Series::new(x)); //! df.push("y", Series::new(y)); //! df.print(); -//! +//! //! Ok(()) //! } //! ``` @@ -270,7 +270,6 @@ //! //! - Gary D. Knott, *Interpolating Splines*, Birkhäuser Boston, MA, (2000). /// - [Wikipedia - Irwin-Hall distribution](https://en.wikipedia.org/wiki/Irwin%E2%80%93Hall_distribution#Special_cases) - use self::SplineError::{NotEnoughNodes, NotEqualNodes, NotEqualSlopes, RedundantNodeX}; #[allow(unused_imports)] use crate::structure::matrix::*; @@ -278,16 +277,17 @@ use crate::structure::matrix::*; use crate::structure::polynomial::*; #[allow(unused_imports)] use crate::structure::vector::*; +use crate::traits::matrix::LinearAlgebra; #[allow(unused_imports)] use crate::util::non_macro::*; use crate::util::useful::zip_range; +use anyhow::{bail, Result}; use peroxide_num::PowOps; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use std::cmp::{max, min}; use std::convert::From; use std::ops::{Index, Range}; -use anyhow::{Result, bail}; pub trait Spline { fn eval(&self, t: f64) -> T; @@ -702,11 +702,7 @@ impl PolynomialSpline for CubicHermiteSpline { } impl CubicHermiteSpline { - pub fn from_nodes_with_slopes( - node_x: &[f64], - node_y: &[f64], - m: &[f64], - ) -> Result { + pub fn from_nodes_with_slopes(node_x: &[f64], node_y: &[f64], m: &[f64]) -> Result { let n = node_x.len(); if n < 3 { bail!(NotEnoughNodes); @@ -744,11 +740,7 @@ impl CubicHermiteSpline { }) } - pub fn from_nodes( - node_x: &[f64], - node_y: &[f64], - slope_method: SlopeMethod, - ) -> Result { + pub fn from_nodes(node_x: &[f64], node_y: &[f64], slope_method: SlopeMethod) -> Result { match slope_method { SlopeMethod::Akima => CubicHermiteSpline::from_nodes_with_slopes( node_x, @@ -933,7 +925,11 @@ pub struct UnitCubicBasis { impl UnitCubicBasis { pub fn new(x_min: f64, x_max: f64, scale: f64) -> Self { - Self { x_min, x_max, scale } + Self { + x_min, + x_max, + scale, + } } pub fn eval(&self, x: f64) -> f64 { @@ -966,17 +962,17 @@ impl UnitCubicBasis { /// ```rust /// use peroxide::fuga::*; /// use core::ops::Range; -/// +/// /// # #[allow(unused_variables)] /// fn main() -> anyhow::Result<()> { /// let cubic_b_spline = CubicBSplineBases::from_interval((0f64, 1f64), 5); /// let x = linspace(0f64, 1f64, 1000); /// let y = cubic_b_spline.eval_vec(&x); -/// +/// /// # #[cfg(feature = "plot")] { /// let mut plt = Plot2D::new(); /// plt.set_domain(x.clone()); -/// +/// /// for basis in &cubic_b_spline.bases { /// plt.insert_image(basis.eval_vec(&x)); /// } @@ -1013,9 +1009,14 @@ impl CubicBSplineBases { let (ranges, bases) = nodes .iter() .zip(nodes.iter().skip(4)) - .map(|(a, b)| (Range { start: *a, end: *b }, UnitCubicBasis::new(*a, *b, 1f64))) + .map(|(a, b)| { + ( + Range { start: *a, end: *b }, + UnitCubicBasis::new(*a, *b, 1f64), + ) + }) .unzip(); - + Self::new(ranges, bases) } @@ -1036,7 +1037,8 @@ impl CubicBSplineBases { } pub fn eval(&self, x: f64) -> f64 { - self.ranges.iter() + self.ranges + .iter() .enumerate() .filter(|(_, range)| range.contains(&x)) .fold(0f64, |acc, (i, _)| { @@ -1108,7 +1110,11 @@ impl BSpline { bail!("The number of knots ({}) should be equal to the number of control points ({}) + degree ({}) + 1", knots.len(), control_points.len(), degree); } - Ok(Self { degree, knots, control_points }) + Ok(Self { + degree, + knots, + control_points, + }) } /// Create new clamped B-Spline @@ -1134,7 +1140,11 @@ impl BSpline { bail!("The number of knots ({}) should be equal to the number of control points ({}) + degree ({}) + 1", knots.len(), control_points.len(), degree); } - Ok(Self { degree, knots, control_points }) + Ok(Self { + degree, + knots, + control_points, + }) } /// Obtain basis function via Cox-de Boor algorithm @@ -1145,7 +1155,9 @@ impl BSpline { // Initialize 0th degree basis functions for (j, B_j) in B.iter_mut().enumerate() { - if (self.knots[i + j] <= t && t < self.knots[i + j + 1]) || (i + j == self.knots.len() - (p + 2) && t == self.knots[i + j + 1]) { + if (self.knots[i + j] <= t && t < self.knots[i + j + 1]) + || (i + j == self.knots.len() - (p + 2) && t == self.knots[i + j + 1]) + { B_j[0] = 1f64; } else { B_j[0] = 0f64; @@ -1154,20 +1166,21 @@ impl BSpline { // Compute the basis functions for higher degree for k in 1..=p { - for j in 0..=(p-k) { - let a = if self.knots[i + j + k] == self.knots[i+j] { + for j in 0..=(p - k) { + let a = if self.knots[i + j + k] == self.knots[i + j] { 0f64 } else { - (t - self.knots[i+j]) / (self.knots[i+j+k] - self.knots[i+j]) + (t - self.knots[i + j]) / (self.knots[i + j + k] - self.knots[i + j]) }; - let b = if self.knots[i + j + k + 1] == self.knots[i+j+1] { + let b = if self.knots[i + j + k + 1] == self.knots[i + j + 1] { 0f64 } else { - (self.knots[i+j+k+1] - t) / (self.knots[i+j+k+1] - self.knots[i+j+1]) + (self.knots[i + j + k + 1] - t) + / (self.knots[i + j + k + 1] - self.knots[i + j + 1]) }; - B[j][k] = a * B[j][k-1] + b * B[j+1][k-1]; + B[j][k] = a * B[j][k - 1] + b * B[j + 1][k - 1]; } } @@ -1182,7 +1195,7 @@ impl Spline<(f64, f64)> for BSpline { let mut x = 0f64; let mut y = 0f64; - for i in 0 .. n { + for i in 0..n { let B = self.cox_de_boor(t, i); x += B * self.control_points[i][0]; y += B * self.control_points[i][1]; diff --git a/src/numerical/utils.rs b/src/numerical/utils.rs index 158aafea..7350ce14 100644 --- a/src/numerical/utils.rs +++ b/src/numerical/utils.rs @@ -1,6 +1,7 @@ -use crate::structure::matrix::*; -use crate::structure::ad::*; use crate::structure::ad::AD::*; +use crate::structure::ad::*; +use crate::structure::matrix::*; +use crate::traits::matrix::MatrixTrait; use crate::util::non_macro::{cat, zeros}; /// Jacobian Matrix @@ -45,7 +46,7 @@ pub fn jacobian) -> Vec>(f: F, x: &Vec) -> Matrix { let mut J = zeros(l2, l); - for i in 0 .. l { + for i in 0..l { x_ad[i][1] = 1f64; let slopes: Vec = f(&x_ad).iter().map(|ad| ad.dx()).collect(); J.subs_col(i, &slopes); @@ -90,7 +91,6 @@ pub fn jacobian) -> Vec>(f: F, x: &Vec) -> Matrix { // JT //} - /// TriDiagonal Matrix Algorithm (TDMA) /// /// # Description diff --git a/src/prelude/mod.rs b/src/prelude/mod.rs index d10ff75e..611bf2eb 100644 --- a/src/prelude/mod.rs +++ b/src/prelude/mod.rs @@ -145,7 +145,7 @@ #[allow(unused_imports)] pub use crate::macros::{julia_macro::*, matlab_macro::*, r_macro::*}; -pub use peroxide_ad::{ad_function, ad_closure}; +pub use peroxide_ad::{ad_closure, ad_function}; pub mod simpler; @@ -153,37 +153,42 @@ pub use crate::traits::{ fp::{FPMatrix, FPVector}, general::Algorithm, math::{InnerProduct, LinearOp, MatrixProduct, Vector, VectorProduct}, + matrix::{MatrixTrait, PQLU, QR, WAZD}, mutable::{MutFP, MutMatrix}, num::Real, pointer::{MatrixPtr, Oxide, Redox, RedoxCommon}, - sugar::{Scalable, ScalableMut, VecOps, ConvToMat}, + sugar::{ConvToMat, Scalable, ScalableMut, VecOps}, }; -pub use peroxide_num::{ExpLogOps, TrigOps, PowOps}; +pub use peroxide_num::{ExpLogOps, PowOps, TrigOps}; pub use simpler::SimpleNorm; +#[cfg(feature = "csv")] +pub use crate::structure::dataframe::WithCSV; #[allow(unused_imports)] pub use crate::structure::{ - ad::*, ad::AD::*, + ad::*, + dataframe::{ + DType, DTypeArray, DTypeValue, DataFrame, Scalar, Series, TypedScalar, TypedVector, + }, matrix::{ combine, diag, gemm, gemv, gen_householder, inv_l, inv_u, matrix, ml_matrix, py_matrix, - r_matrix, Col, Matrix, Row, Shape, PQLU, QR, WAZD, + r_matrix, Col, Matrix, Row, Shape, }, - polynomial::{Polynomial,poly,Calculus,lagrange_polynomial,legendre_polynomial}, + polynomial::{lagrange_polynomial, legendre_polynomial, poly, Calculus, Polynomial}, vector::*, - dataframe::{ - DataFrame, DType, DTypeArray, DTypeValue, Series, Scalar, TypedScalar, TypedVector - }, - //complex::C64, }; -#[cfg(feature="csv")] -pub use crate::structure::dataframe::WithCSV; -#[cfg(feature="nc")] +#[cfg(feature = "nc")] pub use crate::structure::dataframe::WithNetCDF; +#[cfg(feature = "complex")] +#[allow(ambiguous_glob_reexports)] +#[allow(unused_imports)] +pub use crate::complex::{integral::*, matrix::*, vector::*, C64}; + pub use simpler::{solve, SimplerLinearAlgebra}; #[allow(unused_imports)] @@ -194,8 +199,8 @@ pub use crate::statistics::{dist::*, ops::*, rand::*, stat::*}; #[allow(unused_imports)] pub use crate::special::function::{ - beta, erf, erfc, gamma, gaussian, inc_beta, inc_gamma, inv_erf, inv_erfc, inv_inc_gamma, - inv_inc_beta, ln_gamma, phi, poch, + beta, erf, erfc, gamma, gaussian, inc_beta, inc_gamma, inv_erf, inv_erfc, inv_inc_beta, + inv_inc_gamma, ln_gamma, phi, poch, }; #[allow(unused_imports)] @@ -205,19 +210,21 @@ pub use crate::numerical::{ ode::*, optimize::*, root::*, - spline::{cubic_spline, CubicSpline, CubicHermiteSpline, Spline}, + spline::{cubic_spline, CubicHermiteSpline, CubicSpline, Spline}, utils::*, }; -pub use simpler::{eigen, integrate, chebyshev_polynomial, cubic_hermite_spline, lambert_w0, lambert_wm1}; +pub use simpler::{ + chebyshev_polynomial, cubic_hermite_spline, eigen, integrate, lambert_w0, lambert_wm1, +}; #[allow(unused_imports)] pub use crate::statistics::stat::Metric::*; -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] pub use simpler::SimpleParquet; -#[cfg(feature="plot")] +#[cfg(feature = "plot")] pub use crate::util::plot::*; pub use anyhow; diff --git a/src/prelude/simpler.rs b/src/prelude/simpler.rs index 819bc38f..890f7d07 100644 --- a/src/prelude/simpler.rs +++ b/src/prelude/simpler.rs @@ -1,5 +1,3 @@ -#[cfg(feature="parquet")] -use std::error::Error; use crate::numerical::{ eigen, eigen::{Eigen, EigenMethod::Jacobi}, @@ -8,13 +6,16 @@ use crate::numerical::{ spline, spline::{CubicHermiteSpline, SlopeMethod::Quadratic}, }; -use crate::structure::matrix::{self, Matrix}; +#[cfg(feature = "parquet")] +use crate::structure::dataframe::{DataFrame, WithParquet}; +use crate::structure::matrix::Matrix; use crate::structure::polynomial; use crate::traits::math::{Norm, Normed}; -#[cfg(feature="parquet")] -use crate::structure::dataframe::{DataFrame, WithParquet}; -#[cfg(feature="parquet")] +use crate::traits::matrix::{Form, LinearAlgebra, MatrixTrait, SolveKind, PQLU, QR, WAZD}; +#[cfg(feature = "parquet")] use arrow2::io::parquet::write::CompressionOptions; +#[cfg(feature = "parquet")] +use std::error::Error; /// Simple Norm pub trait SimpleNorm: Normed { @@ -28,22 +29,22 @@ pub fn integrate f64 + Copy>(f: F, (a, b): (f64, f64)) -> f64 { } /// Simple Linear algebra -pub trait SimplerLinearAlgebra { - fn back_subs(&self, b: &Vec) -> Vec; - fn forward_subs(&self, b: &Vec) -> Vec; - fn lu(&self) -> matrix::PQLU; - fn waz_diag(&self) -> Option; - fn waz(&self) -> Option; - fn qr(&self) -> matrix::QR; - #[cfg(feature="O3")] - fn cholesky(&self) -> Matrix; - fn rref(&self) -> Matrix; +pub trait SimplerLinearAlgebra { + fn back_subs(&self, b: &[f64]) -> Vec; + fn forward_subs(&self, b: &[f64]) -> Vec; + fn lu(&self) -> PQLU; + fn waz_diag(&self) -> Option>; + fn waz(&self) -> Option>; + fn qr(&self) -> QR; + #[cfg(feature = "O3")] + fn cholesky(&self) -> M; + fn rref(&self) -> M; fn det(&self) -> f64; - fn block(&self) -> (Matrix, Matrix, Matrix, Matrix); - fn inv(&self) -> Matrix; - fn pseudo_inv(&self) -> Matrix; - fn solve(&self, b: &Vec) -> Vec; - fn solve_mat(&self, m: &Matrix) -> Matrix; + fn block(&self) -> (M, M, M, M); + fn inv(&self) -> M; + fn pseudo_inv(&self) -> M; + fn solve(&self, b: &[f64]) -> Vec; + fn solve_mat(&self, m: &M) -> M; fn is_symmetric(&self) -> bool; } @@ -74,73 +75,73 @@ impl SimpleNorm for Matrix { } } -impl SimplerLinearAlgebra for Matrix { - fn back_subs(&self, b: &Vec) -> Vec { - matrix::LinearAlgebra::back_subs(self, b) +impl SimplerLinearAlgebra for Matrix { + fn back_subs(&self, b: &[f64]) -> Vec { + LinearAlgebra::back_subs(self, b) } - fn forward_subs(&self, b: &Vec) -> Vec { - matrix::LinearAlgebra::forward_subs(self, b) + fn forward_subs(&self, b: &[f64]) -> Vec { + LinearAlgebra::forward_subs(self, b) } - fn lu(&self) -> matrix::PQLU { - matrix::LinearAlgebra::lu(self) + fn lu(&self) -> PQLU { + LinearAlgebra::lu(self) } - fn waz_diag(&self) -> Option { - matrix::LinearAlgebra::waz(self, matrix::Form::Diagonal) + fn waz_diag(&self) -> Option> { + LinearAlgebra::waz(self, Form::Diagonal) } - fn waz(&self) -> Option { - matrix::LinearAlgebra::waz(self, matrix::Form::Identity) + fn waz(&self) -> Option> { + LinearAlgebra::waz(self, Form::Identity) } - fn qr(&self) -> matrix::QR { - matrix::LinearAlgebra::qr(self) + fn qr(&self) -> QR { + LinearAlgebra::qr(self) } - #[cfg(feature="O3")] + #[cfg(feature = "O3")] fn cholesky(&self) -> Matrix { - matrix::LinearAlgebra::cholesky(self, matrix::UPLO::Lower) + LinearAlgebra::cholesky(self, UPLO::Lower) } fn rref(&self) -> Matrix { - matrix::LinearAlgebra::rref(self) + LinearAlgebra::rref(self) } fn det(&self) -> f64 { - matrix::LinearAlgebra::det(self) + LinearAlgebra::det(self) } fn block(&self) -> (Matrix, Matrix, Matrix, Matrix) { - matrix::LinearAlgebra::block(self) + LinearAlgebra::block(self) } fn inv(&self) -> Matrix { - matrix::LinearAlgebra::inv(self) + LinearAlgebra::inv(self) } fn pseudo_inv(&self) -> Matrix { - matrix::LinearAlgebra::pseudo_inv(self) + LinearAlgebra::pseudo_inv(self) } - fn solve(&self, b: &Vec) -> Vec { - matrix::LinearAlgebra::solve(self, b, matrix::SolveKind::LU) + fn solve(&self, b: &[f64]) -> Vec { + LinearAlgebra::solve(self, b, SolveKind::LU) } fn solve_mat(&self, m: &Matrix) -> Matrix { - matrix::LinearAlgebra::solve_mat(self, m, matrix::SolveKind::LU) + LinearAlgebra::solve_mat(self, m, SolveKind::LU) } fn is_symmetric(&self) -> bool { - matrix::LinearAlgebra::is_symmetric(self) + LinearAlgebra::is_symmetric(self) } } /// Simple solve #[allow(non_snake_case)] pub fn solve(A: &Matrix, m: &Matrix) -> Matrix { - matrix::solve(A, m, matrix::SolveKind::LU) + crate::traits::matrix::solve(A, m, SolveKind::LU) } /// Simple Chebyshev Polynomial (First Kind) @@ -161,7 +162,7 @@ use crate::special::function::{ /// Returns [`NAN`](f64::NAN) if the given input is smaller than -1/e (≈ -0.36787944117144233). /// /// Accurate to 50 bits. -/// +/// /// Wrapper of `lambert_w_0` function of `lambert_w` crate. /// /// # Reference @@ -176,7 +177,7 @@ pub fn lambert_w0(z: f64) -> f64 { /// Returns [`NAN`](f64::NAN) if the given input is positive or smaller than -1/e (≈ -0.36787944117144233). /// /// Accurate to 50 bits. -/// +/// /// Wrapper of `lambert_w_m1` function of `lambert_w` crate. /// /// # Reference @@ -187,13 +188,13 @@ pub fn lambert_wm1(z: f64) -> f64 { } /// Simple handle parquet -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] pub trait SimpleParquet: Sized { fn write_parquet(&self, path: &str) -> Result<(), Box>; fn read_parquet(path: &str) -> Result>; } -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] impl SimpleParquet for DataFrame { fn write_parquet(&self, path: &str) -> Result<(), Box> { WithParquet::write_parquet(self, path, CompressionOptions::Uncompressed) diff --git a/src/special/function.rs b/src/special/function.rs index e81c86ac..77fbc1d4 100644 --- a/src/special/function.rs +++ b/src/special/function.rs @@ -122,40 +122,38 @@ pub fn phi(x: f64) -> f64 { /// /// Returns [`NAN`](f64::NAN) if the given input is smaller than -1/e (≈ -0.36787944117144233). /// -/// Use [`Precise`](LambertWAccuracyMode::Precise) for 50 bits of accuracy and the [`Simple`](LambertWAccuracyMode::Simple) mode +/// Use [`Precise`](LambertWAccuracyMode::Precise) for 50 bits of accuracy and the [`Simple`](LambertWAccuracyMode::Simple) mode /// for only 24 bits, but with faster execution time. -/// -/// Wrapper of the `lambert_w_0` and `sp_lambert_w_0` functions of the `lambert_w` crate. +/// +/// Wrapper of the `lambert_w_0` and `sp_lambert_w_0` functions of the `puruspe` crate. /// /// # Reference /// /// [Toshio Fukushima, Precise and fast computation of Lambert W function by piecewise minimax rational function approximation with variable transformation](https://www.researchgate.net/publication/346309410_Precise_and_fast_computation_of_Lambert_W_function_by_piecewise_minimax_rational_function_approximation_with_variable_transformation) pub fn lambert_w0(z: f64, mode: LambertWAccuracyMode) -> f64 { match mode { - LambertWAccuracyMode::Precise => lambert_w::lambert_w_0(z), - LambertWAccuracyMode::Simple => lambert_w::sp_lambert_w_0(z), + LambertWAccuracyMode::Precise => puruspe::lambert_w0(z), + LambertWAccuracyMode::Simple => puruspe::sp_lambert_w0(z), } - .unwrap_or(f64::NAN) } /// The secondary branch of the Lambert W function, W_-1(`z`). /// /// Returns [`NAN`](f64::NAN) if the given input is positive or smaller than -1/e (≈ -0.36787944117144233). /// -/// Use [`Precise`](LambertWAccuracyMode::Precise) for 50 bits of accuracy and the [`Simple`](LambertWAccuracyMode::Simple) mode +/// Use [`Precise`](LambertWAccuracyMode::Precise) for 50 bits of accuracy and the [`Simple`](LambertWAccuracyMode::Simple) mode /// for only 24 bits, but with faster execution time. -/// -/// Wrapper of the `lambert_w_m1` and `sp_lambert_w_m1` functions of the `lambert_w` crate. -/// +/// +/// Wrapper of the `lambert_w_m1` and `sp_lambert_w_m1` functions of the `puruspe` crate. +/// /// # Reference /// /// [Toshio Fukushima, Precise and fast computation of Lambert W function by piecewise minimax rational function approximation with variable transformation](https://www.researchgate.net/publication/346309410_Precise_and_fast_computation_of_Lambert_W_function_by_piecewise_minimax_rational_function_approximation_with_variable_transformation) pub fn lambert_wm1(z: f64, mode: LambertWAccuracyMode) -> f64 { match mode { - LambertWAccuracyMode::Precise => lambert_w::lambert_w_m1(z), - LambertWAccuracyMode::Simple => lambert_w::sp_lambert_w_m1(z), + LambertWAccuracyMode::Precise => puruspe::lambert_wm1(z), + LambertWAccuracyMode::Simple => puruspe::sp_lambert_wm1(z), } - .unwrap_or(f64::NAN) } /// Decides the accuracy mode of the Lambert W functions. diff --git a/src/special/lanczos.rs b/src/special/lanczos.rs index eeded0dd..bf563d9d 100644 --- a/src/special/lanczos.rs +++ b/src/special/lanczos.rs @@ -2,6 +2,7 @@ use crate::statistics::ops::{double_factorial, factorial, C}; use crate::structure::matrix::Matrix; +use crate::traits::matrix::MatrixTrait; use crate::traits::pointer::{Oxide, RedoxCommon}; use crate::util::non_macro::zeros; use crate::util::useful::sgn; diff --git a/src/special/mod.rs b/src/special/mod.rs index 8fa1312a..e5dc3ff0 100644 --- a/src/special/mod.rs +++ b/src/special/mod.rs @@ -1,29 +1,29 @@ //! Special functions module -//! +//! //! This module provides implementations of various special mathematical functions //! commonly used in statistical and scientific computing. It includes: -//! +//! //! - Basic special functions: //! - Gaussian (Normal) function //! - Gamma function and its logarithm //! - Pochhammer symbol (rising factorial) -//! +//! //! - Incomplete special functions: //! - Regularized incomplete gamma function and its inverse //! - Regularized incomplete beta function and its inverse -//! +//! //! - Error functions: //! - Error function (erf) and its complement (erfc) //! - Inverse error function and inverse complementary error function -//! +//! //! - Other functions: //! - Beta function //! - Phi function (CDF of the standard normal distribution) //! - Lambert W function (principal branch W₀ and secondary branch W₋₁) -//! +//! //! Many of these functions are implemented using efficient numerical approximations -//! or by wrapping functions from other crates (e.g., `puruspe`, `lambert_w`). -//! +//! or by wrapping functions from other crates (e.g., `puruspe`). +//! //! The module also includes an enum `LambertWAccuracyMode` to control the //! accuracy-speed trade-off for Lambert W function calculations. diff --git a/src/statistics/dist.rs b/src/statistics/dist.rs index b409c197..ee150aca 100644 --- a/src/statistics/dist.rs +++ b/src/statistics/dist.rs @@ -234,15 +234,15 @@ use self::rand::distributions::uniform::SampleUniform; use self::rand::prelude::*; pub use self::OPDist::*; pub use self::TPDist::*; -use crate::traits::fp::FPVector; use crate::special::function::*; +use crate::traits::fp::FPVector; //use statistics::rand::ziggurat; +use self::WeightedUniformError::*; use crate::statistics::{ops::C, stat::Statistics}; use crate::util::non_macro::{linspace, seq}; use crate::util::useful::{auto_zip, find_interval}; +use anyhow::{bail, Result}; use std::f64::consts::E; -use self::WeightedUniformError::*; -use anyhow::{Result, bail}; /// One parameter distribution /// @@ -271,7 +271,7 @@ pub enum TPDist> { pub struct WeightedUniform> { weights: Vec, sum: T, - intervals: Vec<(T, T)> + intervals: Vec<(T, T)>, } #[derive(Debug, Clone, Copy)] @@ -286,7 +286,9 @@ impl std::fmt::Display for WeightedUniformError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { WeightedUniformError::AllZeroWeightError => write!(f, "all weights are zero"), - WeightedUniformError::LengthMismatchError => write!(f, "weights and intervals have different length"), + WeightedUniformError::LengthMismatchError => { + write!(f, "weights and intervals have different length") + } WeightedUniformError::NoNonZeroIntervalError => write!(f, "no non-zero interval found"), WeightedUniformError::EmptyWeightError => write!(f, "weights are empty"), } @@ -295,11 +297,11 @@ impl std::fmt::Display for WeightedUniformError { impl WeightedUniform { /// Create a new weighted uniform distribution - /// + /// /// # Examples /// ``` /// use peroxide::fuga::*; - /// + /// /// fn main() -> Result<(), Box> { /// let weights = vec![1f64, 3f64, 0f64, 2f64]; /// let intervals = vec![0f64, 1f64, 2f64, 4f64, 5f64]; @@ -336,32 +338,31 @@ impl WeightedUniform { } } - let sum = weights.iter() + let sum = weights + .iter() .zip(intervals.iter()) .fold(0f64, |acc, (w, (a, b))| acc + w * (b - a)); - Ok( - WeightedUniform { - weights, - sum, - intervals - } - ) + Ok(WeightedUniform { + weights, + sum, + intervals, + }) } /// Create WeightedUniform from max pooling - /// + /// /// # Examples /// ``` /// use peroxide::fuga::*; - /// + /// /// fn main() -> Result<(), Box> { /// let w = WeightedUniform::from_max_pool_1d(f, (-2f64, 3f64), 10, 1e-3)?; /// w.weights().print(); /// /// Ok(()) /// } - /// + /// /// fn f(x: f64) -> f64 { /// if x.abs() < 1f64 { /// 1f64 - x.abs() @@ -371,40 +372,39 @@ impl WeightedUniform { /// } /// ``` pub fn from_max_pool_1d(f: F, (a, b): (f64, f64), n: usize, eps: f64) -> Result - where F: Fn(f64) -> f64 + Copy { + where + F: Fn(f64) -> f64 + Copy, + { // Find non-zero intervals let mut a = a; let mut b = b; let trial = seq(a, b, eps); - for i in 0 .. trial.len() { + for i in 0..trial.len() { let x = trial[i]; if f(x) > 0f64 { - a = if i > 0 { trial[i-1] } else { x }; + a = if i > 0 { trial[i - 1] } else { x }; break; } } - for i in (0 .. trial.len()).rev() { + for i in (0..trial.len()).rev() { let x = trial[i]; if f(x) > 0f64 { - b = if i < trial.len() - 1 { trial[i+1] } else { x }; + b = if i < trial.len() - 1 { trial[i + 1] } else { x }; break; } } if a >= b { bail!(NoNonZeroIntervalError); } - let domain = linspace(a, b, n+1); + let domain = linspace(a, b, n + 1); // Find intervals let intervals = auto_zip(&domain); // Find weights - let weights: Vec = intervals.iter() - .map( - |(a, b)| { - seq(*a, *b+eps, eps).reduce(0f64, |acc, x| acc.max(f(x))) - } - ) + let weights: Vec = intervals + .iter() + .map(|(a, b)| seq(*a, *b + eps, eps).reduce(0f64, |acc, x| acc.max(f(x)))) .collect(); Self::new(weights, domain) @@ -419,11 +419,19 @@ impl WeightedUniform { } pub fn domain_linspace(&self, n: usize) -> Vec { - linspace(self.intervals[0].0, self.intervals[self.intervals.len()-1].1, n) + linspace( + self.intervals[0].0, + self.intervals[self.intervals.len() - 1].1, + n, + ) } pub fn domain_seq(&self, step: f64) -> Vec { - seq(self.intervals[0].0, self.intervals[self.intervals.len()-1].1, step) + seq( + self.intervals[0].0, + self.intervals[self.intervals.len() - 1].1, + step, + ) } pub fn sum(&self) -> f64 { @@ -433,15 +441,19 @@ impl WeightedUniform { pub fn update_weights(&mut self, weights: Vec) { assert_eq!(self.intervals.len(), weights.len()); self.weights = weights; - self.sum = self.weights.iter() + self.sum = self + .weights + .iter() .zip(self.intervals.iter()) .fold(0f64, |acc, (w, (a, b))| acc + w * (b - a)); } pub fn update_intervals(&mut self, intervals: Vec) { - assert_eq!(self.weights.len()+1, intervals.len()); + assert_eq!(self.weights.len() + 1, intervals.len()); self.intervals = auto_zip(&intervals); - self.sum = self.weights.iter() + self.sum = self + .weights + .iter() .zip(self.intervals.iter()) .fold(0f64, |acc, (w, (a, b))| acc + w * (b - a)); } @@ -493,7 +505,11 @@ impl> ParametricDist for Weight fn params(&self) -> Self::Parameter { let weights = self.weights.iter().map(|x| (*x).into()).collect(); - let intervals = self.intervals.iter().map(|(x, y)| ((*x).into(), (*y).into())).collect(); + let intervals = self + .intervals + .iter() + .map(|(x, y)| ((*x).into(), (*y).into())) + .collect(); (weights, intervals) } } @@ -619,11 +635,7 @@ impl> RNG for TPDist { Binomial(num, mu) => { let binom = rand_distr::Binomial::new(*num as u64, (*mu).into()).unwrap(); - binom - .sample_iter(rng) - .take(n) - .map(|t| t as f64) - .collect() + binom.sample_iter(rng).take(n).map(|t| t as f64).collect() } Normal(m, s) => { @@ -789,10 +801,12 @@ impl RNG for WeightedUniform { let ics: Vec = w.sample_iter(&mut rng_clip).take(n).collect(); *rng = rng_clip; - ics.into_iter().map(|idx| { - let (l, r) = self.intervals[idx]; - rng.gen_range(l ..=r) - }).collect::>() + ics.into_iter() + .map(|idx| { + let (l, r) = self.intervals[idx]; + rng.gen_range(l..=r) + }) + .collect::>() } fn pdf>(&self, x: S) -> f64 { @@ -812,11 +826,11 @@ impl RNG for WeightedUniform { return 1f64; } let idx = find_interval(self.intervals(), x); - self.weights[0 ..=idx].iter() - .zip(self.intervals[0 ..=idx].iter()) - .fold(0f64, |acc, (w, (a, b))| { - acc + w * (b - a) - }) / self.sum + self.weights[0..=idx] + .iter() + .zip(self.intervals[0..=idx].iter()) + .fold(0f64, |acc, (w, (a, b))| acc + w * (b - a)) + / self.sum } } @@ -912,16 +926,23 @@ impl Statistics for WeightedUniform { type Value = f64; fn mean(&self) -> Self::Value { - self.intervals().iter().zip(self.weights().iter()) - .map(|((l, r), w)| (r.powi(2)-l.powi(2))/2f64 * w) - .sum::() / self.sum + self.intervals() + .iter() + .zip(self.weights().iter()) + .map(|((l, r), w)| (r.powi(2) - l.powi(2)) / 2f64 * w) + .sum::() + / self.sum } fn var(&self) -> Self::Value { let mean = self.mean(); - self.intervals().iter().zip(self.weights().iter()) + self.intervals() + .iter() + .zip(self.weights().iter()) .map(|((l, r), w)| w * (r.powi(3) - l.powi(3)) / 3f64) - .sum::() / self.sum - mean * mean + .sum::() + / self.sum + - mean * mean } fn sd(&self) -> Self::Value { diff --git a/src/statistics/rand.rs b/src/statistics/rand.rs index 36590085..1e577fbe 100644 --- a/src/statistics/rand.rs +++ b/src/statistics/rand.rs @@ -24,9 +24,9 @@ extern crate rand; use self::rand::distributions::uniform::SampleUniform; use self::rand::prelude::*; +use crate::statistics::dist::{WeightedUniform, RNG}; #[allow(unused_imports)] use crate::structure::matrix::*; -use crate::statistics::dist::{RNG, WeightedUniform}; /// Small random number generator from seed /// @@ -466,7 +466,9 @@ pub fn ziggurat(rng: &mut ThreadRng, sigma: f64) -> f64 { /// Ok(()) /// } pub fn prs(f: F, n: usize, (a, b): (f64, f64), m: usize, eps: f64) -> anyhow::Result> -where F: Fn(f64) -> f64 + Copy { +where + F: Fn(f64) -> f64 + Copy, +{ let mut rng = thread_rng(); let mut result = vec![0f64; n]; @@ -482,7 +484,7 @@ where F: Fn(f64) -> f64 + Copy { if weight <= 0f64 { continue; } else { - let y = rng.gen_range(0f64 ..=weight); + let y = rng.gen_range(0f64..=weight); if y <= f(x) { result[n - left_num] = x; @@ -527,8 +529,17 @@ where F: Fn(f64) -> f64 + Copy { /// /// Ok(()) /// } -pub fn prs_with_rng(f: F, n: usize, (a, b): (f64, f64), m: usize, eps: f64, rng: &mut R) -> anyhow::Result> - where F: Fn(f64) -> f64 + Copy { +pub fn prs_with_rng( + f: F, + n: usize, + (a, b): (f64, f64), + m: usize, + eps: f64, + rng: &mut R, +) -> anyhow::Result> +where + F: Fn(f64) -> f64 + Copy, +{ let mut result = vec![0f64; n]; let w = WeightedUniform::from_max_pool_1d(f, (a, b), m, eps)?; @@ -542,7 +553,7 @@ pub fn prs_with_rng(f: F, n: usize, (a, b): (f64, f64), m: us if weight <= 0f64 { continue; } else { - let y = rng.gen_range(0f64 ..=weight); + let y = rng.gen_range(0f64..=weight); if y <= f(x) { result[n - left_num] = x; diff --git a/src/statistics/stat.rs b/src/statistics/stat.rs index 9590a9e1..035785ca 100644 --- a/src/statistics/stat.rs +++ b/src/statistics/stat.rs @@ -151,7 +151,7 @@ use std::fmt; use self::QType::*; //use crate::structure::dataframe::*; use crate::structure::matrix::*; -use crate::traits::fp::FPVector; +use crate::traits::matrix::{LinearAlgebra, MatrixTrait}; use order_stat::kth_by; /// Statistics Trait @@ -174,6 +174,8 @@ impl Statistics for Vec { /// Mean /// + /// Uses welfords online algorithm for numerically stable computation. + /// /// # Examples /// ``` /// #[macro_use] @@ -186,11 +188,20 @@ impl Statistics for Vec { /// } /// ``` fn mean(&self) -> f64 { - self.reduce(0f64, |x, y| x + y) / (self.len() as f64) + let mut xn = 0f64; + let mut n = 0f64; + + for x in self.iter() { + n += 1f64; + xn += (x - xn) / n; + } + xn } /// Variance /// + /// Uses welfords online algorithm for numerically stable computation. + /// /// # Examples /// ``` /// #[macro_use] @@ -203,17 +214,18 @@ impl Statistics for Vec { /// } /// ``` fn var(&self) -> f64 { - let mut ss = 0f64; - let mut s = 0f64; - let mut l = 0f64; - - for x in self.into_iter() { - ss += x.powf(2f64); - s += *x; - l += 1f64; + let mut xn = 0f64; + let mut n = 0f64; + let mut m2n: f64 = 0f64; + + for x in self.iter() { + n += 1f64; + let diff_1 = x - xn; + xn += diff_1 / n; + m2n += diff_1 * (x - xn); } - assert_ne!(l, 1f64); - (ss / l - (s / l).powf(2f64)) * l / (l - 1f64) + assert_ne!(n, 1f64); + m2n / (n - 1f64) } /// Standard Deviation @@ -241,6 +253,91 @@ impl Statistics for Vec { } } +impl Statistics for Vec { + type Array = Vec; + type Value = f32; + + /// Mean + /// + /// Uses welfords online algorithm for numerically stable computation. + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// + /// fn main() { + /// let a = c!(1,2,3,4,5); + /// assert_eq!(a.mean(), 3.0); + /// } + /// ``` + fn mean(&self) -> f32 { + let mut xn = 0f32; + let mut n = 0f32; + + for x in self.iter() { + n += 1f32; + xn += (x - xn) / n; + } + xn + } + + /// Variance + /// + /// Uses welfords online algorithm for numerically stable computation. + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// + /// fn main() { + /// let a = c!(1,2,3,4,5); + /// assert_eq!(a.var(), 2.5); + /// } + /// ``` + fn var(&self) -> f32 { + let mut xn = 0f32; + let mut n = 0f32; + let mut m2n: f32 = 0f32; + + for x in self.iter() { + n += 1f32; + let diff_1 = x - xn; + xn += diff_1 / n; + m2n += diff_1 * (x - xn); + } + assert_ne!(n, 1f32); + m2n / (n - 1f32) + } + + /// Standard Deviation + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// + /// fn main() { + /// let a = c!(1,2,3); + /// assert!(nearly_eq(a.sd(), 1f64)); // Floating Number Error + /// } + /// ``` + fn sd(&self) -> f32 { + self.var().sqrt() + } + + fn cov(&self) -> Vec { + unimplemented!() + } + fn cor(&self) -> Vec { + unimplemented!() + } +} + impl Statistics for Matrix { type Array = Matrix; type Value = Vec; @@ -571,7 +668,7 @@ pub fn quantile(v: &Vec, qtype: QType) -> Vec { // Confusion Matrix // ============================================================================= /// Confusion Matrix -/// +/// /// * `TP` : True Positive /// * `TN` : True Negative /// * `FP` : False Positive @@ -619,7 +716,7 @@ pub struct ConfusionMatrix { impl ConfusionMatrix { /// Create Confusion Matrix - /// + /// /// # Examples /// ``` /// use peroxide::fuga::*; @@ -656,12 +753,7 @@ impl ConfusionMatrix { } } - Self { - TP, - TN, - FP, - FN, - } + Self { TP, TN, FP, FN } } /// Condition Positive @@ -809,10 +901,10 @@ impl ConfusionMatrix { /// To Matrix pub fn to_matrix(&self) -> Matrix { let mut m = matrix(vec![0f64; 4], 2, 2, Row); - m[(0,0)] = self.TP as f64; - m[(0,1)] = self.FP as f64; - m[(1,0)] = self.FN as f64; - m[(1,1)] = self.TN as f64; + m[(0, 0)] = self.TP as f64; + m[(0, 1)] = self.FP as f64; + m[(1, 0)] = self.FN as f64; + m[(1, 1)] = self.TN as f64; m } diff --git a/src/structure/ad.rs b/src/structure/ad.rs index e850c716..19fa725b 100644 --- a/src/structure/ad.rs +++ b/src/structure/ad.rs @@ -105,17 +105,12 @@ //! ``` //! -use peroxide_num::{ExpLogOps, PowOps, TrigOps}; +use self::AD::{AD0, AD1, AD2}; use crate::statistics::ops::C; -use crate::traits::{ - stable::StableFn, - fp::FPVector, - math::Vector, - sugar::VecOps, -}; -use std::iter::{FromIterator, DoubleEndedIterator, ExactSizeIterator}; +use crate::traits::{fp::FPVector, math::Vector, stable::StableFn, sugar::VecOps}; +use peroxide_num::{ExpLogOps, PowOps, TrigOps}; +use std::iter::{DoubleEndedIterator, ExactSizeIterator, FromIterator}; use std::ops::{Add, Div, Index, IndexMut, Mul, Neg, Sub}; -use self::AD::{AD0, AD1, AD2}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum AD { @@ -197,8 +192,8 @@ impl AD { pub fn empty(&self) -> Self { match self { AD0(_) => AD0(0f64), - AD1(_,_) => AD1(0f64, 0f64), - AD2(_,_,_) => AD2(0f64, 0f64, 0f64), + AD1(_, _) => AD1(0f64, 0f64), + AD2(_, _, _) => AD2(0f64, 0f64, 0f64), } } @@ -231,10 +226,10 @@ impl AD { pub fn set_ddx(&mut self, ddx: f64) { match self { AD0(_) => panic!("Can't set ddx for AD0"), - AD1(_,_) => panic!("Can't set ddx for AD1"), - AD2(_,_,ddt) => { + AD1(_, _) => panic!("Can't set ddx for AD1"), + AD2(_, _, ddt) => { *ddt = ddx; - } + } } } @@ -314,8 +309,8 @@ impl AD { unsafe fn x_ptr(&self) -> Option<*const f64> { match self { AD0(x) => Some(x), - AD1(x,_) => Some(x), - AD2(x,_,_) => Some(x), + AD1(x, _) => Some(x), + AD2(x, _, _) => Some(x), } } @@ -323,8 +318,8 @@ impl AD { unsafe fn dx_ptr(&self) -> Option<*const f64> { match self { AD0(_) => None, - AD1(_,dx) => Some(dx), - AD2(_,dx,_) => Some(dx), + AD1(_, dx) => Some(dx), + AD2(_, dx, _) => Some(dx), } } @@ -332,32 +327,32 @@ impl AD { unsafe fn ddx_ptr(&self) -> Option<*const f64> { match self { AD0(_) => None, - AD1(_,_) => None, - AD2(_,_,ddx) => Some(ddx), + AD1(_, _) => None, + AD2(_, _, ddx) => Some(ddx), } } unsafe fn x_mut_ptr(&mut self) -> Option<*mut f64> { match self { AD0(x) => Some(&mut *x), - AD1(x,_) => Some(&mut *x), - AD2(x,_,_) => Some(&mut *x), + AD1(x, _) => Some(&mut *x), + AD2(x, _, _) => Some(&mut *x), } } unsafe fn dx_mut_ptr(&mut self) -> Option<*mut f64> { match self { AD0(_) => None, - AD1(_,dx) => Some(&mut *dx), - AD2(_,dx,_) => Some(&mut *dx), + AD1(_, dx) => Some(&mut *dx), + AD2(_, dx, _) => Some(&mut *dx), } } unsafe fn ddx_mut_ptr(&mut self) -> Option<*mut f64> { match self { AD0(_) => None, - AD1(_,_) => None, - AD2(_,_,ddx) => Some(&mut *ddx), + AD1(_, _) => None, + AD2(_, _, ddx) => Some(&mut *ddx), } } } @@ -513,7 +508,7 @@ impl<'a> Iterator for ADIterMut<'a> { let l = self.ad.len(); if self.index + self.r_index < l { unsafe { - let result= match self.index { + let result = match self.index { 0 => self.ad.x_mut_ptr(), 1 => self.ad.dx_mut_ptr(), 2 => self.ad.ddx_mut_ptr(), @@ -522,7 +517,7 @@ impl<'a> Iterator for ADIterMut<'a> { self.index += 1; match result { None => None, - Some(ad) => Some(&mut *ad) + Some(ad) => Some(&mut *ad), } } } else { @@ -622,10 +617,7 @@ impl Add for AD { let ord = self.order().max(rhs.order()); let (a, b) = (self.to_order(ord), rhs.to_order(ord)); - a.into_iter() - .zip(b) - .map(|(x, y)| x + y) - .collect() + a.into_iter().zip(b).map(|(x, y)| x + y).collect() } } @@ -636,10 +628,7 @@ impl Sub for AD { let ord = self.order().max(rhs.order()); let (a, b) = (self.to_order(ord), rhs.to_order(ord)); - a.into_iter() - .zip(b) - .map(|(x, y)| x - y) - .collect() + a.into_iter().zip(b).map(|(x, y)| x - y).collect() } } @@ -673,10 +662,16 @@ impl Div for AD { let mut z = a; z[0] = a[0] / b[0]; let y0 = 1f64 / b[0]; - for i in 1 .. z.len() { + for i in 1..z.len() { let mut s = 0f64; - for (j, (&y1, &z1)) in b.iter().skip(1).take(i).zip(z.iter().take(i).rev()).enumerate() { - s += (C(i, j+1) as f64) * y1 * z1; + for (j, (&y1, &z1)) in b + .iter() + .skip(1) + .take(i) + .zip(z.iter().take(i).rev()) + .enumerate() + { + s += (C(i, j + 1) as f64) * y1 * z1; } z[i] = y0 * (a[i] - s); } @@ -690,12 +685,15 @@ impl ExpLogOps for AD { fn exp(&self) -> Self { let mut z = self.empty(); z[0] = self[0].exp(); - for i in 1 .. z.len() { - z[i] = z.iter() + for i in 1..z.len() { + z[i] = z + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&z1, &x1))| x + (C(i-1, k) as f64) * x1 * z1); + .fold(0f64, |x, (k, (&z1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * z1 + }); } z } @@ -704,10 +702,16 @@ impl ExpLogOps for AD { let mut z = self.empty(); z[0] = self[0].ln(); let x0 = 1f64 / self[0]; - for i in 1 .. z.len() { + for i in 1..z.len() { let mut s = 0f64; - for (k, (&z1, &x1)) in z.iter().skip(1).take(i-1).zip(self.iter().skip(1).take(i-1).rev()).enumerate() { - s += (C(i-1, k+1) as f64) * z1 * x1; + for (k, (&z1, &x1)) in z + .iter() + .skip(1) + .take(i - 1) + .zip(self.iter().skip(1).take(i - 1).rev()) + .enumerate() + { + s += (C(i - 1, k + 1) as f64) * z1 * x1; } z[i] = x0 * (self[i] - s); } @@ -732,7 +736,7 @@ impl PowOps for AD { fn powi(&self, n: i32) -> Self { let mut z = *self; - for _i in 1 .. n { + for _i in 1..n { z = z * *self; } z @@ -742,10 +746,16 @@ impl PowOps for AD { let ln_x = self.ln(); let mut z = self.empty(); z[0] = self.x().powf(f); - for i in 1 .. z.len() { + for i in 1..z.len() { let mut s = 0f64; - for (j, (&z1, &ln_x1)) in z.iter().skip(1).take(i-1).zip(ln_x.iter().skip(1).take(i-1).rev()).enumerate() { - s += (C(i-1, j+1) as f64) * z1 * ln_x1; + for (j, (&z1, &ln_x1)) in z + .iter() + .skip(1) + .take(i - 1) + .zip(ln_x.iter().skip(1).take(i - 1).rev()) + .enumerate() + { + s += (C(i - 1, j + 1) as f64) * z1 * ln_x1; } z[i] = f * (z[0] * ln_x[i] + s); } @@ -757,10 +767,16 @@ impl PowOps for AD { let p = y * ln_x; let mut z = self.empty(); z[0] = self.x().powf(y.x()); - for n in 1 .. z.len() { + for n in 1..z.len() { let mut s = 0f64; - for (k, (&z1, &p1)) in z.iter().skip(1).take(n-1).zip(p.iter().skip(1).take(n-1).rev()).enumerate() { - s += (C(n-1, k+1) as f64) * z1 * p1; + for (k, (&z1, &p1)) in z + .iter() + .skip(1) + .take(n - 1) + .zip(p.iter().skip(1).take(n - 1).rev()) + .enumerate() + { + s += (C(n - 1, k + 1) as f64) * z1 * p1; } z[n] = z[0] * p[n] + s; } @@ -778,17 +794,23 @@ impl TrigOps for AD { let mut v = self.empty(); u[0] = self[0].sin(); v[0] = self[0].cos(); - for i in 1 .. u.len() { - u[i] = v.iter() + for i in 1..u.len() { + u[i] = v + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&v1, &x1))| x + (C(i-1, k) as f64) * x1 * v1); - v[i] = u.iter() + .fold(0f64, |x, (k, (&v1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * v1 + }); + v[i] = u + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&u1, &x1))| x + (C(i-1, k) as f64) * x1 * u1); + .fold(0f64, |x, (k, (&u1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * u1 + }); } (u, v) } @@ -803,17 +825,23 @@ impl TrigOps for AD { let mut v = self.empty(); u[0] = self[0].sinh(); v[0] = self[0].cosh(); - for i in 1 .. u.len() { - u[i] = v.iter() + for i in 1..u.len() { + u[i] = v + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&v1, &x1))| x + (C(i-1, k) as f64) * x1 * v1); - v[i] = u.iter() + .fold(0f64, |x, (k, (&v1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * v1 + }); + v[i] = u + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&u1, &x1))| x + (C(i-1, k) as f64) * x1 * u1); + .fold(0f64, |x, (k, (&u1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * u1 + }); } u } @@ -823,17 +851,23 @@ impl TrigOps for AD { let mut v = self.empty(); u[0] = self[0].sinh(); v[0] = self[0].cosh(); - for i in 1 .. u.len() { - u[i] = v.iter() + for i in 1..u.len() { + u[i] = v + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&v1, &x1))| x + (C(i-1, k) as f64) * x1 * v1); - v[i] = u.iter() + .fold(0f64, |x, (k, (&v1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * v1 + }); + v[i] = u + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&u1, &x1))| x + (C(i-1, k) as f64) * x1 * u1); + .fold(0f64, |x, (k, (&u1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * u1 + }); } v } @@ -843,17 +877,23 @@ impl TrigOps for AD { let mut v = self.empty(); u[0] = self[0].sinh(); v[0] = self[0].cosh(); - for i in 1 .. u.len() { - u[i] = v.iter() + for i in 1..u.len() { + u[i] = v + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&v1, &x1))| x + (C(i-1, k) as f64) * x1 * v1); - v[i] = u.iter() + .fold(0f64, |x, (k, (&v1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * v1 + }); + v[i] = u + .iter() .take(i) .zip(self.iter().skip(1).take(i).rev()) .enumerate() - .fold(0f64, |x, (k, (&u1, &x1))| x + (C(i-1, k) as f64) * x1 * u1); + .fold(0f64, |x, (k, (&u1, &x1))| { + x + (C(i - 1, k) as f64) * x1 * u1 + }); } u / v } @@ -862,12 +902,15 @@ impl TrigOps for AD { let dx = 1f64 / (1f64 - self.powi(2)).sqrt(); let mut z = self.empty(); z[0] = self[0].asin(); - for n in 1 .. z.len() { - z[n] = dx.iter() + for n in 1..z.len() { + z[n] = dx + .iter() .take(n) .zip(self.iter().skip(1).take(n).rev()) .enumerate() - .fold(0f64, |s, (k, (&q1, &x1))| s + (C(n-1, k) as f64) * x1 * q1); + .fold(0f64, |s, (k, (&q1, &x1))| { + s + (C(n - 1, k) as f64) * x1 * q1 + }); } z } @@ -876,12 +919,15 @@ impl TrigOps for AD { let dx = (-1f64) / (1f64 - self.powi(2)).sqrt(); let mut z = self.empty(); z[0] = self[0].acos(); - for n in 1 .. z.len() { - z[n] = dx.iter() + for n in 1..z.len() { + z[n] = dx + .iter() .take(n) .zip(self.iter().skip(1).take(n).rev()) .enumerate() - .fold(0f64, |s, (k, (&q1, &x1))| s + (C(n-1, k) as f64) * x1 * q1); + .fold(0f64, |s, (k, (&q1, &x1))| { + s + (C(n - 1, k) as f64) * x1 * q1 + }); } z } @@ -890,12 +936,15 @@ impl TrigOps for AD { let dx = 1f64 / (1f64 + self.powi(2)); let mut z = self.empty(); z[0] = self[0].atan(); - for n in 1 .. z.len() { - z[n] = dx.iter() + for n in 1..z.len() { + z[n] = dx + .iter() .take(n) .zip(self.iter().skip(1).take(n).rev()) .enumerate() - .fold(0f64, |s, (k, (&q1, &x1))| s + (C(n-1, k) as f64) * x1 * q1); + .fold(0f64, |s, (k, (&q1, &x1))| { + s + (C(n - 1, k) as f64) * x1 * q1 + }); } z } @@ -904,12 +953,15 @@ impl TrigOps for AD { let dx = 1f64 / (1f64 + self.powi(2)).sqrt(); let mut z = self.empty(); z[0] = self[0].asinh(); - for n in 1 .. z.len() { - z[n] = dx.iter() + for n in 1..z.len() { + z[n] = dx + .iter() .take(n) .zip(self.iter().skip(1).take(n).rev()) .enumerate() - .fold(0f64, |s, (k, (&q1, &x1))| s + (C(n-1, k) as f64) * x1 * q1); + .fold(0f64, |s, (k, (&q1, &x1))| { + s + (C(n - 1, k) as f64) * x1 * q1 + }); } z } @@ -918,12 +970,15 @@ impl TrigOps for AD { let dx = 1f64 / (self.powi(2) - 1f64).sqrt(); let mut z = self.empty(); z[0] = self[0].acosh(); - for n in 1 .. z.len() { - z[n] = dx.iter() + for n in 1..z.len() { + z[n] = dx + .iter() .take(n) .zip(self.iter().skip(1).take(n).rev()) .enumerate() - .fold(0f64, |s, (k, (&q1, &x1))| s + (C(n-1, k) as f64) * x1 * q1); + .fold(0f64, |s, (k, (&q1, &x1))| { + s + (C(n - 1, k) as f64) * x1 * q1 + }); } z } @@ -932,12 +987,15 @@ impl TrigOps for AD { let dx = 1f64 / (1f64 - self.powi(2)); let mut z = self.empty(); z[0] = self[0].atanh(); - for n in 1 .. z.len() { - z[n] = dx.iter() + for n in 1..z.len() { + z[n] = dx + .iter() .take(n) .zip(self.iter().skip(1).take(n).rev()) .enumerate() - .fold(0f64, |s, (k, (&q1, &x1))| s + (C(n-1, k) as f64) * x1 * q1); + .fold(0f64, |s, (k, (&q1, &x1))| { + s + (C(n - 1, k) as f64) * x1 * q1 + }); } z } @@ -1237,7 +1295,10 @@ impl AD> StableFn for ADFn { impl) -> Vec> StableFn> for ADFn { type Output = Vec; fn call_stable(&self, target: Vec) -> Self::Output { - ((self.f)(target.iter().map(|&t| AD::from(t)).collect())).iter().map(|&t| t.x()).collect() + ((self.f)(target.iter().map(|&t| AD::from(t)).collect())) + .iter() + .map(|&t| t.x()) + .collect() } } @@ -1251,7 +1312,10 @@ impl) -> Vec> StableFn> for ADFn { impl<'a, F: Fn(&Vec) -> Vec> StableFn<&'a Vec> for ADFn { type Output = Vec; fn call_stable(&self, target: &'a Vec) -> Self::Output { - ((self.f)(&target.iter().map(|&t| AD::from(t)).collect())).iter().map(|&t| t.x()).collect() + ((self.f)(&target.iter().map(|&t| AD::from(t)).collect())) + .iter() + .map(|&t| t.x()) + .collect() } } @@ -1300,26 +1364,33 @@ impl FPVector for Vec { fn fmap(&self, f: F) -> Self where - F: Fn(Self::Scalar) -> Self::Scalar { + F: Fn(Self::Scalar) -> Self::Scalar, + { self.iter().map(|&x| f(x)).collect() } fn reduce(&self, init: T, f: F) -> Self::Scalar where - F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, - T: Into { - self.iter().fold(init.into(), |x, &y| f(x,y)) + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, + T: Into, + { + self.iter().fold(init.into(), |x, &y| f(x, y)) } fn zip_with(&self, f: F, other: &Self) -> Self where - F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar { - self.iter().zip(other.iter()).map(|(&x, &y)| f(x, y)).collect() + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, + { + self.iter() + .zip(other.iter()) + .map(|(&x, &y)| f(x, y)) + .collect() } fn filter(&self, f: F) -> Self where - F: Fn(Self::Scalar) -> bool { + F: Fn(Self::Scalar) -> bool, + { self.iter().filter(|&x| f(*x)).cloned().collect() } diff --git a/src/structure/dataframe.rs b/src/structure/dataframe.rs index 51b03107..e32c3393 100644 --- a/src/structure/dataframe.rs +++ b/src/structure/dataframe.rs @@ -39,7 +39,7 @@ //! fn at_raw(&self, i: usize) -> T; //! fn push(&mut self, elem: T); //! } -//! ``` +//! ``` //! //! * `Series` methods //! @@ -58,7 +58,7 @@ //! * All integer & float types can be exchanged. //! * `Bool, Char` can be changed to `Str` or `U8` only. //! * `U8` can be changed to all types. -//! +//! //! ### 3. Example //! //! ```rust @@ -72,7 +72,7 @@ //! //! a.print(); // print for Series //! b.dtype.print(); // print for dtype of Series (=Char) -//! c.as_type(U8); // Bool => U8 +//! c.as_type(U8); // Bool => U8 //! //! assert_eq!(c.dtype, U8); //! } @@ -187,8 +187,7 @@ //! //! * `nc` feature should be required //! * `libnetcdf` dependency should be required -//! * `Char`, `Bool` are saved as `U8` type. Thus, for reading `Char` or `Bool` type nc file, -//! explicit type casting is required. +//! * `Char`, `Bool` are saved as `U8` type. Thus, for reading `Char` or `Bool` type nc file, explicit type casting is required. //! //! ``` //! #[macro_use] @@ -215,19 +214,18 @@ //! Ok(()) //! } //! ``` -//! +//! //! * `WithParquet` trait -//! +//! //! ```ignore //! pub trait WithParquet: Sized { //! fn write_parquet(&self, file_path: &str, compression: CompressionOptions) -> Result<(), Box>; //! fn read_parquet(file_path: &str) -> Result>; //! } //! ``` -//! +//! //! * `parquet` feature should be required -//! * `Char` is saved with `String` type. Thus, for reading `Char` type parquet file, -//! the output type is `String`. +//! * `Char` is saved with `String` type. Thus, for reading `Char` type parquet file, the output type is `String`. //! * **Caution** : For different length `Bool` type column, missing values are filled with `false`. //! ``` //! #[macro_use] @@ -255,56 +253,35 @@ //! } //! ``` -#[cfg(feature="csv")] +use crate::traits::math::Vector; +use crate::util::{print::LowerExpWithPlus, useful::tab}; +use std::cmp::{max, min}; +#[cfg(feature = "csv")] use std::collections::HashMap; +#[cfg(any(feature = "csv", feature = "nc", feature = "parquet"))] +use std::error::Error; use std::fmt; use std::ops::{Index, IndexMut}; -use std::cmp::{max, min}; -#[cfg(any(feature="csv", feature="nc", feature="parquet"))] -use std::error::Error; -use crate::util::{ - useful::tab, - print::LowerExpWithPlus, -}; -use crate::traits::math::Vector; -use DType::{ - USIZE,U8,U16,U32,U64, - ISIZE,I8,I16,I32,I64, - F32,F64,Bool,Char,Str -}; +use DType::{Bool, Char, Str, F32, F64, I16, I32, I64, I8, ISIZE, U16, U32, U64, U8, USIZE}; -#[cfg(feature="csv")] -use csv::{ReaderBuilder, WriterBuilder}; -#[cfg(feature="nc")] -use netcdf::{ - types::VariableType, - variable::{VariableMut, Variable}, - Numeric, -}; -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] use arrow2::{ - array::{ - PrimitiveArray, - BooleanArray, - Utf8Array, - Array - }, + array::{Array, BooleanArray, PrimitiveArray, Utf8Array}, chunk::Chunk, - datatypes::{Field, DataType, Schema}, - types::NativeType, + datatypes::{DataType, Field, Schema}, + io::parquet::read::{infer_schema, read_metadata, FileReader}, io::parquet::write::{ - WriteOptions, - CompressionOptions, - RowGroupIterator, - Version, - FileWriter, - Encoding + CompressionOptions, Encoding, FileWriter, RowGroupIterator, Version, WriteOptions, }, - io::parquet::read::{ - read_metadata, - infer_schema, - FileReader, - } + types::NativeType, +}; +#[cfg(feature = "csv")] +use csv::{ReaderBuilder, WriterBuilder}; +#[cfg(feature = "nc")] +use netcdf::{ + types::VariableType, + variable::{Variable, VariableMut}, + Numeric, }; // ============================================================================= @@ -448,7 +425,9 @@ pub struct Scalar { // Traits // ============================================================================= pub trait TypedScalar { - fn new(s: T) -> Self where Self: Sized; + fn new(s: T) -> Self + where + Self: Sized; fn unwrap(self) -> T; } @@ -461,13 +440,13 @@ pub trait TypedVector { fn push(&mut self, elem: T); fn map T>(&self, f: F) -> Self; fn mut_map(&mut self, f: F); - fn fold T>(&self, init: T, f: F) -> T; + fn fold T>(&self, init: T, f: F) -> T; fn filter bool>(&self, f: F) -> Self; fn take(&self, n: usize) -> Self; fn skip(&self, n: usize) -> Self; fn take_while bool>(&self, f: F) -> Self; fn skip_while bool>(&self, f: F) -> Self; - fn zip_with T>(&self, f: F, other: &Self) -> Self; + fn zip_with T>(&self, f: F, other: &Self) -> Self; } // ============================================================================= @@ -490,7 +469,7 @@ macro_rules! impl_typed_scalar { } } } - } + }; } macro_rules! impl_typed_vector { @@ -578,13 +557,14 @@ macro_rules! impl_typed_vector { let v: Vec<$type> = self.to_vec(); let w: Vec<$type> = other.to_vec(); Series::new( - v.into_iter().zip(w.into_iter()) + v.into_iter() + .zip(w.into_iter()) .map(|(x, y)| f(x, y)) - .collect::>() + .collect::>(), ) } } - } + }; } macro_rules! dtype_case { @@ -693,7 +673,7 @@ macro_rules! set_space { st2 } } - _ => $elem.to_string() + _ => $elem.to_string(), } }}; @@ -703,14 +683,14 @@ macro_rules! set_space { let elem: f32 = $elem.unwrap(); $space = max( $space, - min(elem.fmt_lower_exp(2).len(), elem.to_string().len()) + min(elem.fmt_lower_exp(2).len(), elem.to_string().len()), ); } F64 => { let elem: f64 = $elem.unwrap(); $space = max( $space, - min(elem.fmt_lower_exp(2).len(), elem.to_string().len()) + min(elem.fmt_lower_exp(2).len(), elem.to_string().len()), ); } _ => { @@ -724,14 +704,10 @@ macro_rules! format_float_vec { ($self:expr) => {{ let mut result = String::new(); result.push_str("["); - for i in 0 .. $self.len() { + for i in 0..$self.len() { let st1 = $self[i].fmt_lower_exp(2); let st2 = $self[i].to_string(); - let st = if st1.len() < st2.len() { - st1 - } else { - st2 - }; + let st = if st1.len() < st2.len() { st1 } else { st2 }; result.push_str(&st); if i == $self.len() - 1 { break; @@ -757,7 +733,7 @@ macro_rules! string_cast_vec { let y: Vec<$ty1> = $to_vec; let x: Vec = y.into_iter().map(|x| x.to_string()).collect(); $wrapper(x) - }} + }}; } macro_rules! type_parse_vec { @@ -815,20 +791,18 @@ macro_rules! dtype_cast_vec { ($dt1:expr, $dt2:expr, $to_vec:expr, $wrapper:expr) => {{ match $dt1 { USIZE => dtype_cast_vec_part!(usize, $dt2, $to_vec, $wrapper), - U8 => { - match $dt2 { - Bool => { - let y: Vec = $to_vec; - let x: Vec = y.into_iter().map(|x| x != 0).collect(); - $wrapper(x) - }, - Char => { - let y: Vec = $to_vec; - let x: Vec = y.into_iter().map(|x| x as char).collect(); - $wrapper(x) - }, - _ => dtype_cast_vec_part!(u8, $dt2, $to_vec, $wrapper) + U8 => match $dt2 { + Bool => { + let y: Vec = $to_vec; + let x: Vec = y.into_iter().map(|x| x != 0).collect(); + $wrapper(x) } + Char => { + let y: Vec = $to_vec; + let x: Vec = y.into_iter().map(|x| x as char).collect(); + $wrapper(x) + } + _ => dtype_cast_vec_part!(u8, $dt2, $to_vec, $wrapper), }, U16 => dtype_cast_vec_part!(u16, $dt2, $to_vec, $wrapper), U32 => dtype_cast_vec_part!(u32, $dt2, $to_vec, $wrapper), @@ -841,28 +815,24 @@ macro_rules! dtype_cast_vec { F32 => dtype_cast_vec_part!(f32, $dt2, $to_vec, $wrapper), F64 => dtype_cast_vec_part!(f64, $dt2, $to_vec, $wrapper), Str => dtype_parse_vec_part!($dt2, $to_vec, $wrapper), - Char => { - match $dt2 { - Str => string_cast_vec!(char, $to_vec, $wrapper), - U8 => { - let y: Vec = $to_vec; - let x: Vec = y.into_iter().map(|x| x as u8).collect(); - $wrapper(x) - }, - _ => panic!("Can't convert char type to {}", $dt2), + Char => match $dt2 { + Str => string_cast_vec!(char, $to_vec, $wrapper), + U8 => { + let y: Vec = $to_vec; + let x: Vec = y.into_iter().map(|x| x as u8).collect(); + $wrapper(x) } - } - Bool => { - match $dt2 { - Str => string_cast_vec!(bool, $to_vec, $wrapper), - U8 => { - let y: Vec = $to_vec; - let x: Vec = y.into_iter().map(|x| x as u8).collect(); - $wrapper(x) - }, - _ => panic!("Can't convert bool type to {}", $dt2), + _ => panic!("Can't convert char type to {}", $dt2), + }, + Bool => match $dt2 { + Str => string_cast_vec!(bool, $to_vec, $wrapper), + U8 => { + let y: Vec = $to_vec; + let x: Vec = y.into_iter().map(|x| x as u8).collect(); + $wrapper(x) } - } + _ => panic!("Can't convert bool type to {}", $dt2), + }, } }}; } @@ -875,7 +845,7 @@ fn to_string(x: T) -> String { x.to_string() } -#[cfg(feature= "nc")] +#[cfg(feature = "nc")] fn dtype_to_vtype(dt: DType) -> netcdf::types::BasicType { match dt { USIZE => netcdf::types::BasicType::Uint64, @@ -896,7 +866,7 @@ fn dtype_to_vtype(dt: DType) -> netcdf::types::BasicType { } } -#[cfg(feature= "nc")] +#[cfg(feature = "nc")] fn vtype_to_dtype(dv: netcdf::types::BasicType) -> DType { match dv { netcdf::types::BasicType::Ubyte => U8, @@ -913,20 +883,26 @@ fn vtype_to_dtype(dv: netcdf::types::BasicType) -> DType { } } -#[cfg(feature= "nc")] +#[cfg(feature = "nc")] fn nc_put_value(var: &mut VariableMut, v: Vec) -> Result<(), netcdf::error::Error> { var.put_values(&v, None, None) } -#[cfg(feature= "nc")] -fn nc_read_value(val: &Variable, v: Vec) -> Result where Series: TypedVector { +#[cfg(feature = "nc")] +fn nc_read_value( + val: &Variable, + v: Vec, +) -> Result +where + Series: TypedVector, +{ let mut v = v; v.resize_with(val.len(), Default::default); val.values_to(&mut v, None, None)?; Ok(Series::new(v.clone())) } -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] fn dtype_to_arrow(dt: DType) -> DataType { match dt { USIZE => DataType::UInt64, @@ -947,7 +923,7 @@ fn dtype_to_arrow(dt: DType) -> DataType { } } -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] fn arrow_to_dtype(dt: DataType) -> DType { match dt { DataType::Boolean => Bool, @@ -963,27 +939,29 @@ fn arrow_to_dtype(dt: DataType) -> DType { DataType::Float32 => F32, DataType::Float64 => F64, DataType::Utf8 => Str, - _ => unimplemented!() + _ => unimplemented!(), } } -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] macro_rules! dtype_case_to_arrow { ($ty:ty, $to_arr:expr, $value:expr, $chunk_vec:expr; $length:expr) => {{ let v: Vec<$ty> = $value; - let v_wrap = (0usize..$length).map(|i| { - if i < v.len() { - Some(v[i].clone()) - } else { - None - } - }).collect::>(); + let v_wrap = (0usize..$length) + .map(|i| { + if i < v.len() { + Some(v[i].clone()) + } else { + None + } + }) + .collect::>(); let arr = $to_arr(v_wrap); $chunk_vec.push(arr.boxed()) - }} + }}; } -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] macro_rules! dtype_match_to_arrow { ($dtype:expr, $value:expr, $chunk_vec:expr; $length:expr) => {{ match $dtype { @@ -1010,26 +988,38 @@ macro_rules! dtype_match_to_arrow { }}; } -#[cfg(feature= "parquet")] -fn parquet_read_value(arr: &Box, _v: Vec) -> Result where Series: TypedVector { +#[cfg(feature = "parquet")] +fn parquet_read_value( + arr: &Box, + _v: Vec, +) -> Result +where + Series: TypedVector, +{ let x = arr.as_any().downcast_ref::>().unwrap(); let x = x.values_iter().cloned().collect::>(); Ok(Series::new(x)) } -fn add_vec + Clone>(v: Vec, w: Vec) -> Series -where Series: TypedVector { +fn add_vec + Clone>(v: Vec, w: Vec) -> Series +where + Series: TypedVector, +{ Series::new(v.into_iter().zip(w).map(|(x, y)| x + y).collect::>()) } -fn sub_vec + Clone>(v: Vec, w: Vec) -> Series -where Series: TypedVector { +fn sub_vec + Clone>(v: Vec, w: Vec) -> Series +where + Series: TypedVector, +{ Series::new(v.into_iter().zip(w).map(|(x, y)| x - y).collect::>()) } -fn mul_scalar + Clone + Copy>(v: Vec, s: T) -> Series -where Series: TypedVector { +fn mul_scalar + Clone + Copy>(v: Vec, s: T) -> Series +where + Series: TypedVector, +{ Series::new(v.into_iter().map(|x| x * s).collect::>()) } @@ -1141,7 +1131,7 @@ impl Series { pub fn at(&self, i: usize) -> Scalar { dtype_match!(self.dtype, self.at_raw(i), Scalar::new) } - + /// Length for Series pub fn len(&self) -> usize { dtype_match!(self.dtype, self.as_slice().to_vec(), len; Vec) @@ -1196,7 +1186,7 @@ impl Vector for Series { assert_eq!(self.dtype, rhs.dtype, "DTypes are not same (add_vec)"); dtype_match!( N; - self.dtype, + self.dtype, self.to_vec(), |x| add_vec(x, rhs.to_vec()); Vec @@ -1374,12 +1364,9 @@ impl fmt::Display for Scalar { impl DataFrame { /// Declare new DataFrame with `Vec` pub fn new(v: Vec) -> Self { - let ics = (0usize .. v.len()).map(|x| x.to_string()).collect(); + let ics = (0usize..v.len()).map(|x| x.to_string()).collect(); - Self { - data: v, - ics, - } + Self { data: v, ics } } pub fn header(&self) -> &Vec { @@ -1398,8 +1385,12 @@ impl DataFrame { /// Push new pair of head, Series to DataFrame pub fn push(&mut self, name: &str, series: Series) { - if self.ics.len() > 0 { - assert_eq!(self.ics.iter().find(|x| x.as_str() == name), None, "Repetitive index!"); + if !self.ics.is_empty() { + assert_eq!( + self.ics.iter().find(|x| x.as_str() == name), + None, + "Repetitive index!" + ); } self.ics.push(name.to_string()); self.data.push(series); @@ -1417,9 +1408,12 @@ impl DataFrame { } pub fn spread(&self) -> String { - let r: usize = self.data.iter().fold(0, |max_len, column| max(max_len, column.len())); + let r: usize = self + .data + .iter() + .fold(0, |max_len, column| max(max_len, column.len())); let h = self.header(); - + let mut result = String::new(); if r > 100 { @@ -1427,15 +1421,15 @@ impl DataFrame { result.push_str(&tab("", lc1)); let mut space_vec: Vec = vec![]; - for i in 0 .. self.data.len() { + for i in 0..self.data.len() { let v = &self[i]; let mut space = 0usize; - for j in 0 .. v.len().min(5) { + for j in 0..v.len().min(5) { let elem = v.at(j); set_space!(elem, space); } - if v.len() >= r-5 { - for j in v.len()-5 .. v.len() { + if v.len() >= r - 5 { + for j in v.len() - 5..v.len() { let elem = v.at(j); set_space!(elem, space); } @@ -1449,31 +1443,30 @@ impl DataFrame { space_vec.push(space); } result.push('\n'); - - for i in 0 .. 5 { + + for i in 0..5 { result.push_str(&tab(&format!("r[{}]", i), lc1)); - for j in 0 .. self.data.len() { + for j in 0..self.data.len() { let v = &self[j]; let space = space_vec[j]; if i < v.len() { let elem = v.at(i); let st = set_space!(elem); result.push_str(&tab(&st, space)); - } else { - result.push_str(&tab("", space)); + } else { + result.push_str(&tab("", space)); } } result.push('\n'); } result.push_str(&tab("...", lc1)); - for j in 0 .. self.data.len() { - let space = space_vec[j]; + for &space in space_vec.iter() { result.push_str(&tab("...", space)); } result.push('\n'); - for i in r-5 .. r { + for i in r - 5..r { result.push_str(&tab(&format!("r[{}]", i), lc1)); - for j in 0 .. self.data.len() { + for j in 0..self.data.len() { let v = &self[j]; let space = space_vec[j]; if i < v.len() { @@ -1484,7 +1477,7 @@ impl DataFrame { result.push_str(&tab("", space)); } } - if i == r-1 { + if i == r - 1 { break; } result.push('\n'); @@ -1495,10 +1488,10 @@ impl DataFrame { result.push_str(&tab("", 5)); let mut space_vec: Vec = vec![]; - for i in 0 .. self.data.len() { + for i in 0..self.data.len() { let v = &self[i]; let mut space = 0usize; - for j in 0 .. v.len() { + for j in 0..v.len() { let elem = v.at(j); set_space!(elem, space) } @@ -1512,9 +1505,9 @@ impl DataFrame { } result.push('\n'); - for i in 0 .. r { + for i in 0..r { result.push_str(&tab(&format!("r[{}]", i), 5)); - for j in 0 .. self.data.len() { + for j in 0..self.data.len() { let v = &self[j]; let space = space_vec[j]; if i < v.len() { @@ -1532,7 +1525,7 @@ impl DataFrame { } result } - + /// Type casting for DataFrame /// /// # Examples @@ -1556,7 +1549,11 @@ impl DataFrame { /// } /// ``` pub fn as_types(&mut self, dtypes: Vec) { - assert_eq!(self.data.len(), dtypes.len(), "Length of dtypes are not compatible with DataFrame"); + assert_eq!( + self.data.len(), + dtypes.len(), + "Length of dtypes are not compatible with DataFrame" + ); for (i, dtype) in dtypes.into_iter().enumerate() { self[i].as_type(dtype); } @@ -1637,13 +1634,13 @@ impl fmt::Display for DataFrame { // ============================================================================= /// To handle CSV file format -#[cfg(feature="csv")] +#[cfg(feature = "csv")] pub trait WithCSV: Sized { fn write_csv(&self, file_path: &str) -> Result<(), Box>; fn read_csv(file_path: &str, delimiter: char) -> Result>; } -#[cfg(feature="csv")] +#[cfg(feature = "csv")] impl WithCSV for DataFrame { /// Write csv file fn write_csv(&self, file_path: &str) -> Result<(), Box> { @@ -1653,11 +1650,9 @@ impl WithCSV for DataFrame { .iter() .fold(0, |max_len, column| max(max_len, column.len())); let c: usize = self.data.len(); - wtr.write_record( - self.header().clone() - )?; - - for i in 0 .. r { + wtr.write_record(self.header().clone())?; + + for i in 0..r { let mut record: Vec = vec!["".to_string(); c]; for (j, v) in self.data.iter().enumerate() { if i < v.len() { @@ -1699,14 +1694,14 @@ impl WithCSV for DataFrame { } /// To handle with NetCDF file format -#[cfg(feature= "nc")] +#[cfg(feature = "nc")] pub trait WithNetCDF: Sized { fn write_nc(&self, file_path: &str) -> Result<(), Box>; fn read_nc(file_path: &str) -> Result>; fn read_nc_by_header(file_path: &str, header: Vec<&str>) -> Result>; } -#[cfg(feature= "nc")] +#[cfg(feature = "nc")] impl WithNetCDF for DataFrame { /// write netcdf file fn write_nc(&self, file_path: &str) -> Result<(), Box> { @@ -1720,7 +1715,11 @@ impl WithNetCDF for DataFrame { match v.dtype { dtype if dtype.is_numeric() => { let vtype = dtype_to_vtype(dtype); - let var = &mut f.add_variable_with_type(h, &[&dim_name], &VariableType::Basic(vtype))?; + let var = &mut f.add_variable_with_type( + h, + &[&dim_name], + &VariableType::Basic(vtype), + )?; dtype_match!(N; dtype, v.to_vec(), |v| nc_put_value(var, v); Vec)?; } Str => { @@ -1754,7 +1753,7 @@ impl WithNetCDF for DataFrame { let v_slice: &[u8] = v.as_slice(); var.put_values(v_slice, None, None)?; } - _ => unreachable!() + _ => unreachable!(), } } @@ -1769,7 +1768,7 @@ impl WithNetCDF for DataFrame { let h = v.name(); if v.vartype().is_string() { let mut data: Vec = vec![Default::default(); v.len()]; - for i in 0 .. v.len() { + for i in 0..v.len() { data[i] = v.string_value(Some(&[i]))?; } df.push(&h, Series::new(data)); @@ -1778,7 +1777,6 @@ impl WithNetCDF for DataFrame { let series = dtype_match!(N; dtype, vec![], |vec| nc_read_value(&v, vec); Vec)?; df.push(&h, series); } - } Ok(df) } @@ -1818,7 +1816,7 @@ impl WithNetCDF for DataFrame { }; if v.vartype().is_string() { let mut data: Vec = vec![Default::default(); v.len()]; - for i in 0 .. v.len() { + for i in 0..v.len() { data[i] = v.string_value(Some(&[i]))?; } df.push(&h, Series::new(data)); @@ -1833,17 +1831,27 @@ impl WithNetCDF for DataFrame { } /// To handle parquet format -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] pub trait WithParquet { - fn write_parquet(&self, file_path: &str, compression: CompressionOptions) -> Result<(), Box>; - fn read_parquet(file_path: &str) -> Result> where Self: Sized; + fn write_parquet( + &self, + file_path: &str, + compression: CompressionOptions, + ) -> Result<(), Box>; + fn read_parquet(file_path: &str) -> Result> + where + Self: Sized; // fn read_parquet_by_header(file_path: &str, header: Vec<&str>) -> Result> where Self: Sized; } -#[cfg(feature="parquet")] +#[cfg(feature = "parquet")] impl WithParquet for DataFrame { /// Write DataFrame to parquet - fn write_parquet(&self, file_path: &str, compression: CompressionOptions) -> Result<(), Box> { + fn write_parquet( + &self, + file_path: &str, + compression: CompressionOptions, + ) -> Result<(), Box> { let file = std::fs::File::create(file_path)?; let mut schema_vec = vec![]; @@ -1862,7 +1870,7 @@ impl WithParquet for DataFrame { let schema = Schema::from(schema_vec); let l = arr_vec.len(); let chunk = Chunk::new(arr_vec); - let encodings = (0 .. l).map(|_| vec![Encoding::Plain]).collect::>(); + let encodings = (0..l).map(|_| vec![Encoding::Plain]).collect::>(); let options = WriteOptions { write_statistics: true, compression, @@ -1870,12 +1878,8 @@ impl WithParquet for DataFrame { data_pagesize_limit: None, }; - let row_groups = RowGroupIterator::try_new( - vec![Ok(chunk)].into_iter(), - &schema, - options, - encodings, - )?; + let row_groups = + RowGroupIterator::try_new(vec![Ok(chunk)].into_iter(), &schema, options, encodings)?; let mut writer = FileWriter::try_new(file, schema, options)?; @@ -1889,7 +1893,10 @@ impl WithParquet for DataFrame { } /// Read parquet to DataFrame - fn read_parquet(file_path: &str) -> Result> where Self: Sized { + fn read_parquet(file_path: &str) -> Result> + where + Self: Sized, + { let mut df = DataFrame::new(vec![]); let mut reader = std::fs::File::open(file_path)?; @@ -1920,15 +1927,21 @@ impl WithParquet for DataFrame { } Char => { let data = arr.as_any().downcast_ref::>().unwrap(); - let data = data.values_iter().map(|t| t.chars().next().unwrap()).collect::>(); + let data = data + .values_iter() + .map(|t| t.chars().next().unwrap()) + .collect::>(); df.push(&h, Series::new(data)) } Str => { let data = arr.as_any().downcast_ref::>().unwrap(); - let data = data.values_iter().map(|t| t.to_string()).collect::>(); + let data = data + .values_iter() + .map(|t| t.to_string()) + .collect::>(); df.push(&h, Series::new(data)) } - _ => unreachable!() + _ => unreachable!(), } } } diff --git a/src/structure/matrix.rs b/src/structure/matrix.rs index 797792ca..62dfc722 100644 --- a/src/structure/matrix.rs +++ b/src/structure/matrix.rs @@ -192,7 +192,7 @@ //! To write columns or rows, `DataFrame` and `nc` feature could be the best choice. //! //! * `nc` feature should be required - `netcdf` or `libnetcdf` are prerequisites. -//! +//! //! ```no_run //! #[macro_use] //! extern crate peroxide; @@ -462,8 +462,7 @@ //! //! ## Tips for LU, Det, Inverse //! -//! * If you save `self.lu()` rather than the direct use of `self.det()` or `self.lu()` then you -//! can get better performance (via memoization) +//! * If you save `self.lu()` rather than the direct use of `self.det()` or `self.lu()` then you can get better performance (via memoization) //! //! ```rust //! use peroxide::fuga::*; @@ -596,10 +595,10 @@ //! } //! ``` -#[cfg(feature="csv")] +#[cfg(feature = "csv")] extern crate csv; -#[cfg(feature="csv")] +#[cfg(feature = "csv")] use self::csv::{ReaderBuilder, StringRecord, WriterBuilder}; #[cfg(feature = "O3")] @@ -609,30 +608,35 @@ extern crate lapack; #[cfg(feature = "O3")] use blas::{daxpy, dgemm, dgemv}; #[cfg(feature = "O3")] -use lapack::{dgecon, dgeqrf, dgetrf, dgetri, dgetrs, dorgqr, dgesvd, dpotrf}; +use lapack::{dgecon, dgeqrf, dgesvd, dgetrf, dgetri, dgetrs, dorgqr, dpotrf}; +#[cfg(feature = "parallel")] +use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub use self::Shape::{Col, Row}; use crate::numerical::eigen::{eigen, EigenMethod}; +use crate::structure::dataframe::{Series, TypedVector}; +#[cfg(feature = "parallel")] +use crate::traits::math::{ParallelInnerProduct, ParallelNormed}; +use crate::traits::sugar::ScalableMut; use crate::traits::{ - general::Algorithm, fp::{FPMatrix, FPVector}, + general::Algorithm, math::{InnerProduct, LinearOp, MatrixProduct, Norm, Normed, Vector}, + matrix::{Form, LinearAlgebra, MatrixTrait, SolveKind, PQLU, QR, SVD, WAZD}, mutable::MutMatrix, }; use crate::util::{ - low_level::{swap_vec_ptr, copy_vec_ptr}, + low_level::{copy_vec_ptr, swap_vec_ptr}, non_macro::{cbind, eye, rbind, zeros}, useful::{nearly_eq, tab}, }; -use crate::structure::dataframe::{Series, TypedVector}; +use peroxide_num::{ExpLogOps, Numeric, PowOps, TrigOps}; use std::cmp::{max, min}; pub use std::error::Error; use std::fmt; use std::ops::{Add, Div, Index, IndexMut, Mul, Neg, Sub}; -use crate::traits::sugar::ScalableMut; -use peroxide_num::{ExpLogOps, PowOps, TrigOps, Numeric}; pub type Perms = Vec<(usize, usize)>; @@ -817,14 +821,16 @@ impl PartialEq for Matrix { /// Main matrix structure #[allow(dead_code)] -impl Matrix { +impl MatrixTrait for Matrix { + type Scalar = f64; + /// Raw pointer for `self.data` - pub fn ptr(&self) -> *const f64 { + fn ptr(&self) -> *const f64 { &self.data[0] as *const f64 } /// Raw mutable pointer for `self.data` - pub fn mut_ptr(&mut self) -> *mut f64 { + fn mut_ptr(&mut self) -> *mut f64 { &mut self.data[0] as *mut f64 } @@ -838,7 +844,7 @@ impl Matrix { /// let b = a.as_slice(); /// assert_eq!(b, &[1f64,2f64,3f64,4f64]); /// ``` - pub fn as_slice(&self) -> &[f64] { + fn as_slice(&self) -> &[f64] { &self.data[..] } @@ -854,7 +860,7 @@ impl Matrix { /// assert_eq!(b, &[5f64, 2f64, 3f64, 4f64]); /// assert_eq!(a, matrix(vec![5,2,3,4], 2, 2, Col)); /// ``` - pub fn as_mut_slice(&mut self) -> &mut [f64] { + fn as_mut_slice(&mut self) -> &mut [f64] { &mut self.data[..] } @@ -871,7 +877,7 @@ impl Matrix { /// let b = a.change_shape(); /// assert_eq!(b.shape, Col); /// ``` - pub fn change_shape(&self) -> Self { + fn change_shape(&self) -> Self { let r = self.row; let c = self.col; assert_eq!(r * c, self.data.len()); @@ -912,7 +918,7 @@ impl Matrix { /// let b = a.change_shape(); /// assert_eq!(b.shape, Col); /// ``` - pub fn change_shape_mut(&mut self) { + fn change_shape_mut(&mut self) { let r = self.row; let c = self.col; assert_eq!(r * c, self.data.len()); @@ -952,7 +958,7 @@ impl Matrix { /// // r[0] 1 3 /// // r[1] 2 4 /// ``` - pub fn spread(&self) -> String { + fn spread(&self) -> String { assert_eq!(self.row * self.col, self.data.len()); let r = self.row; let c = self.col; @@ -973,10 +979,10 @@ impl Matrix { }; return format!( "Result is too large to print - {}x{}\nonly print {}x{} parts:\n{}", - self.row.to_string(), - self.col.to_string(), - key_row.to_string(), - key_col.to_string(), + self.row, + self.col, + key_row, + key_col, part.spread() ); } @@ -988,7 +994,7 @@ impl Matrix { .map( |x| min(format!("{:.4}", x).len(), x.to_string().len()), // Choose minimum of approx vs normal ) - .fold(0, |x, y| max(x, y)) + .fold(0, max) + 1; if space < 5 { @@ -1023,7 +1029,7 @@ impl Matrix { result.push('\n'); } - return result; + result } /// Extract Column @@ -1039,7 +1045,7 @@ impl Matrix { /// assert_eq!(a.col(0), c!(1,3)); /// } /// ``` - pub fn col(&self, index: usize) -> Vec { + fn col(&self, index: usize) -> Vec { assert!(index < self.col); let mut container: Vec = vec![0f64; self.row]; for i in 0..self.row { @@ -1061,7 +1067,7 @@ impl Matrix { /// assert_eq!(a.row(0), c!(1,2)); /// } /// ``` - pub fn row(&self, index: usize) -> Vec { + fn row(&self, index: usize) -> Vec { assert!(index < self.row); let mut container: Vec = vec![0f64; self.col]; for i in 0..self.col { @@ -1083,7 +1089,7 @@ impl Matrix { /// assert_eq!(a.diag(), c!(1,4)); /// } /// ``` - pub fn diag(&self) -> Vec { + fn diag(&self) -> Vec { let mut container = vec![0f64; self.row]; let r = self.row; let c = self.col; @@ -1104,7 +1110,7 @@ impl Matrix { /// let a = matrix(vec![1,2,3,4], 2, 2, Row); /// println!("{}", a); // [[1,3],[2,4]] /// ``` - pub fn transpose(&self) -> Self { + fn transpose(&self) -> Self { match self.shape { Row => matrix(self.data.clone(), self.col, self.row, Col), Col => matrix(self.data.clone(), self.col, self.row, Row), @@ -1124,10 +1130,145 @@ impl Matrix { /// assert_eq!(a.transpose(), a.t()); /// } /// ``` - pub fn t(&self) -> Self { + fn t(&self) -> Self { self.transpose() } + /// Substitute Col + #[inline] + fn subs_col(&mut self, idx: usize, v: &[f64]) { + for i in 0..self.row { + self[(i, idx)] = v[i]; + } + } + + /// Substitute Row + #[inline] + fn subs_row(&mut self, idx: usize, v: &[f64]) { + for j in 0..self.col { + self[(idx, j)] = v[j]; + } + } + + /// From index operations + fn from_index(f: F, size: (usize, usize)) -> Matrix + where + F: Fn(usize, usize) -> G + Copy, + G: Into, + { + let row = size.0; + let col = size.1; + + let mut mat = matrix(vec![0f64; row * col], row, col, Row); + + for i in 0..row { + for j in 0..col { + mat[(i, j)] = f(i, j).into(); + } + } + mat + } + + /// Matrix to `Vec>` + /// + /// To send `Matrix` to `inline-python` + fn to_vec(&self) -> Vec> { + let mut result = vec![vec![0f64; self.col]; self.row]; + for i in 0..self.row { + result[i] = self.row(i); + } + result + } + + fn to_diag(&self) -> Matrix { + assert_eq!(self.row, self.col, "Should be square matrix"); + let mut result = matrix(vec![0f64; self.row * self.col], self.row, self.col, Row); + let diag = self.diag(); + for i in 0..self.row { + result[(i, i)] = diag[i]; + } + result + } + + /// Submatrix + /// + /// # Description + /// Return below elements of matrix to new matrix + /// + /// $$ + /// \begin{pmatrix} + /// \\ddots & & & & \\\\ + /// & start & \\cdots & end.1 & \\\\ + /// & \\vdots & \\ddots & \\vdots & \\\\ + /// & end.0 & \\cdots & end & \\\\ + /// & & & & \\ddots + /// \end{pmatrix} + /// $$ + /// + /// # Examples + /// ``` + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// + /// fn main() { + /// let a = ml_matrix("1 2 3;4 5 6;7 8 9"); + /// let b = ml_matrix("5 6;8 9"); + /// let c = a.submat((1, 1), (2, 2)); + /// assert_eq!(b, c); + /// } + /// ``` + fn submat(&self, start: (usize, usize), end: (usize, usize)) -> Matrix { + let row = end.0 + 1 - start.0; + let col = end.1 + 1 - start.1; + let mut result = matrix(vec![0f64; row * col], row, col, self.shape); + for i in 0..row { + for j in 0..col { + result[(i, j)] = self[(start.0 + i, start.1 + j)]; + } + } + result + } + + /// Substitute matrix to specific position + /// + /// # Description + /// Substitute below elements of matrix + /// + /// $$ + /// \begin{pmatrix} + /// \\ddots & & & & \\\\ + /// & start & \\cdots & end.1 & \\\\ + /// & \\vdots & \\ddots & \\vdots & \\\\ + /// & end.0 & \\cdots & end & \\\\ + /// & & & & \\ddots + /// \end{pmatrix} + /// $$ + /// + /// # Examples + /// ``` + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// + /// fn main() { + /// let mut a = ml_matrix("1 2 3;4 5 6;7 8 9"); + /// let b = ml_matrix("1 2;3 4"); + /// let c = ml_matrix("1 2 3;4 1 2;7 3 4"); + /// a.subs_mat((1,1), (2,2), &b); + /// assert_eq!(a, c); + /// } + /// ``` + fn subs_mat(&mut self, start: (usize, usize), end: (usize, usize), m: &Matrix) { + let row = end.0 - start.0 + 1; + let col = end.1 - start.1 + 1; + for i in 0..row { + for j in 0..col { + self[(start.0 + i, start.1 + j)] = m[(i, j)]; + } + } + } +} + +impl Matrix { /// Write to CSV /// /// # Examples @@ -1143,7 +1284,7 @@ impl Matrix { /// Ok(()) /// } /// ``` - #[cfg(feature="csv")] + #[cfg(feature = "csv")] pub fn write(&self, file_path: &str) -> Result<(), Box> { let mut wtr = WriterBuilder::new().from_path(file_path)?; let r = self.row; @@ -1174,7 +1315,7 @@ impl Matrix { /// Ok(()) /// } /// ``` - #[cfg(feature="csv")] + #[cfg(feature = "csv")] pub fn write_round(&self, file_path: &str, round: usize) -> Result<(), Box> { let mut wtr = WriterBuilder::new().from_path(file_path)?; let r = self.row; @@ -1190,7 +1331,7 @@ impl Matrix { Ok(()) } - #[cfg(feature="csv")] + #[cfg(feature = "csv")] pub fn write_with_header( &self, file_path: &str, @@ -1212,7 +1353,7 @@ impl Matrix { Ok(()) } - #[cfg(feature="csv")] + #[cfg(feature = "csv")] pub fn write_with_header_round( &self, file_path: &str, @@ -1260,7 +1401,7 @@ impl Matrix { /// Ok(()) /// } /// ``` - #[cfg(feature="csv")] + #[cfg(feature = "csv")] pub fn read(file_path: &str, header: bool, delimiter: char) -> Result> { let mut rdr = ReaderBuilder::new() .has_headers(header) @@ -1285,159 +1426,6 @@ impl Matrix { Ok(m) } - - /// Should check shape - pub fn subs(&mut self, idx: usize, v: &Vec) { - let p = &mut self.mut_ptr(); - match self.shape { - Row => { - let c = self.col; - unsafe { - p.add(idx * c).copy_from(v.as_ptr(), c); - } - } - Col => { - let r = self.row; - unsafe { - p.add(idx * r).copy_from(v.as_ptr(), r); - } - } - } - } - - /// Substitute Col - #[inline] - pub fn subs_col(&mut self, idx: usize, v: &Vec) { - for i in 0..self.row { - self[(i, idx)] = v[i]; - } - } - - /// Substitute Row - #[inline] - pub fn subs_row(&mut self, idx: usize, v: &Vec) { - for j in 0..self.col { - self[(idx, j)] = v[j]; - } - } - - /// From index operations - pub fn from_index(f: F, size: (usize, usize)) -> Matrix - where - F: Fn(usize, usize) -> G + Copy, - G: Into, - { - let row = size.0; - let col = size.1; - - let mut mat = matrix(vec![0f64; row * col], row, col, Row); - - for i in 0..row { - for j in 0..col { - mat[(i, j)] = f(i, j).into(); - } - } - mat - } - - /// Matrix to `Vec>` - /// - /// To send `Matrix` to `inline-python` - pub fn to_vec(&self) -> Vec> { - let mut result = vec![vec![0f64; self.col]; self.row]; - for i in 0..self.row { - result[i] = self.row(i); - } - result - } - - pub fn to_diag(&self) -> Matrix { - assert_eq!(self.row, self.col, "Should be square matrix"); - let mut result = matrix(vec![0f64; self.row * self.col], self.row, self.col, Row); - let diag = self.diag(); - for i in 0..self.row { - result[(i, i)] = diag[i]; - } - result - } - - /// Submatrix - /// - /// # Description - /// Return below elements of matrix to new matrix - /// - /// $$ - /// \begin{pmatrix} - /// \\ddots & & & & \\\\ - /// & start & \\cdots & end.1 & \\\\ - /// & \\vdots & \\ddots & \\vdots & \\\\ - /// & end.0 & \\cdots & end & \\\\ - /// & & & & \\ddots - /// \end{pmatrix} - /// $$ - /// - /// # Examples - /// ``` - /// extern crate peroxide; - /// use peroxide::fuga::*; - /// - /// fn main() { - /// let a = ml_matrix("1 2 3;4 5 6;7 8 9"); - /// let b = ml_matrix("5 6;8 9"); - /// let c = a.submat((1, 1), (2, 2)); - /// assert_eq!(b, c); - /// } - /// ``` - pub fn submat(&self, start: (usize, usize), end: (usize, usize)) -> Matrix { - let row = end.0 - start.0 + 1; - let col = end.1 - start.1 + 1; - let mut result = matrix(vec![0f64; row * col], row, col, self.shape); - for i in 0 .. row { - for j in 0 .. col { - result[(i, j)] = self[(start.0 + i, start.1 + j)]; - } - } - result - } - - /// Substitute matrix to specific position - /// - /// # Description - /// Substitute below elements of matrix - /// - /// $$ - /// \begin{pmatrix} - /// \\ddots & & & & \\\\ - /// & start & \\cdots & end.1 & \\\\ - /// & \\vdots & \\ddots & \\vdots & \\\\ - /// & end.0 & \\cdots & end & \\\\ - /// & & & & \\ddots - /// \end{pmatrix} - /// $$ - /// - /// # Examples - /// ``` - /// extern crate peroxide; - /// use peroxide::fuga::*; - /// - /// fn main() { - /// let mut a = ml_matrix("1 2 3;4 5 6;7 8 9"); - /// let b = ml_matrix("1 2;3 4"); - /// let c = ml_matrix("1 2 3;4 1 2;7 3 4"); - /// a.subs_mat((1,1), (2,2), &b); - /// assert_eq!(a, c); - /// } - /// ``` - pub fn subs_mat(&mut self, start: (usize, usize), end: (usize, usize), m: &Matrix) { - let row = end.0 - start.0 + 1; - let col = end.1 - start.1 + 1; - for i in 0 .. row { - for j in 0 .. col { - self[(start.0 + i, start.1 + j)] = m[(i, j)]; - } - } - } - /// Matrix from series /// /// # Example @@ -1457,6 +1445,28 @@ impl Matrix { let v: Vec = series.to_vec(); matrix(v, row, col, shape) } + + /// From index operations in parallel + #[cfg(feature = "parallel")] + fn par_from_index(f: F, size: (usize, usize)) -> Matrix + where + F: Fn(usize, usize) -> G + Copy + Send + Sync, + G: Into, + { + let row = size.0; + let col = size.1; + + let data = (0..row) + .into_par_iter() + .flat_map(|i| { + (0..col) + .into_par_iter() + .map(|j| f(i, j).into()) + .collect::>() + }) + .collect::>(); + matrix(data, row, col, Row) + } } // ============================================================================= @@ -1550,6 +1560,16 @@ impl Vector for Matrix { impl Normed for Matrix { type UnsignedScalar = f64; + + /// Norm of Matrix + /// + /// # Example + /// ``` + /// use peroxide::fuga::*; + /// + /// let a = ml_matrix("1 2;3 4"); + /// assert_eq!(a.norm(Norm::F), (1f64 + 4f64 + 9f64 + 16f64).sqrt()); + /// ``` fn norm(&self, kind: Norm) -> f64 { match kind { Norm::F => { @@ -1616,6 +1636,82 @@ impl Normed for Matrix { } } +#[cfg(feature = "parallel")] +impl ParallelNormed for Matrix { + type UnsignedScalar = f64; + + /// Parallel version of norm + /// + /// # Example + /// ``` + /// use peroxide::fuga::*; + /// + /// let a = ml_matrix("1 2;3 4"); + /// assert_eq!(a.par_norm(Norm::F), (1f64 + 4f64 + 9f64 + 16f64).sqrt()); + /// ``` + /// + fn par_norm(&self, kind: Norm) -> f64 { + match kind { + Norm::F => { + let mut s = 0f64; + for i in 0..self.data.len() { + s += self.data[i].powi(2); + } + s.sqrt() + } + Norm::Lpq(p, q) => { + let s = (0..self.col) + .into_par_iter() + .map(|j| { + let s_row = (0..self.row) + .into_iter() + .map(|i| self[(i, j)].powi(p as i32)) + .sum::(); + s_row.powf(q / p) + }) + .sum::(); + s.powf(1f64 / q) + } + Norm::L1 => { + let mut m = f64::MIN; + match self.shape { + Row => self.change_shape().norm(Norm::L1), + Col => { + for c in 0..self.col { + let s = self.col(c).par_iter().sum(); + if s > m { + m = s; + } + } + m + } + } + } + Norm::LInf => { + let mut m = f64::MIN; + match self.shape { + Col => self.change_shape().norm(Norm::LInf), + Row => { + for r in 0..self.row { + let s = self.row(r).iter().sum(); + if s > m { + m = s; + } + } + m + } + } + } + Norm::L2 => { + let a = &self.t() * self; + let eig = eigen(&a, EigenMethod::Jacobi); + eig.eigenvalue.norm(Norm::LInf) + } + Norm::Lp(_) => unimplemented!(), + } + } +} + /// Frobenius inner product impl InnerProduct for Matrix { fn dot(&self, rhs: &Self) -> f64 { @@ -1627,7 +1723,17 @@ impl InnerProduct for Matrix { } } -/// TODO: Transpose +/// Frobenius inner product in parallel +#[cfg(feature = "parallel")] +impl ParallelInnerProduct for Matrix { + fn par_dot(&self, rhs: &Self) -> f64 { + if self.shape == rhs.shape { + self.data.par_dot(&rhs.data) + } else { + self.data.par_dot(&rhs.change_shape().data) + } + } +} /// Matrix as Linear operator for Vector #[allow(non_snake_case)] @@ -2561,6 +2667,8 @@ impl IndexMut<(usize, usize)> for Matrix { // ============================================================================= impl FPMatrix for Matrix { + type Scalar = f64; + fn take_row(&self, n: usize) -> Self { if n >= self.row { return self.clone(); @@ -2643,12 +2751,10 @@ impl FPMatrix for Matrix { /// ```rust /// use peroxide::fuga::*; /// - /// fn main() { - /// let x = ml_matrix("1 2;3 4;5 6"); - /// let y = x.col_map(|c| c.fmap(|t| t - c.mean())); + /// let x = ml_matrix("1 2;3 4;5 6"); + /// let y = x.col_map(|c| c.fmap(|t| t - c.mean())); /// - /// assert_eq!(y, ml_matrix("-2 -2;0 0;2 2")); - /// } + /// assert_eq!(y, ml_matrix("-2 -2;0 0;2 2")); /// ``` fn col_map(&self, f: F) -> Matrix where @@ -2669,12 +2775,10 @@ impl FPMatrix for Matrix { /// ```rust /// use peroxide::fuga::*; /// - /// fn main() { - /// let x = ml_matrix("1 2 3;4 5 6"); - /// let y = x.row_map(|r| r.fmap(|t| t - r.mean())); + /// let x = ml_matrix("1 2 3;4 5 6"); + /// let y = x.row_map(|r| r.fmap(|t| t - r.mean())); /// - /// assert_eq!(y, ml_matrix("-1 0 1;-1 0 1")); - /// } + /// assert_eq!(y, ml_matrix("-1 0 1;-1 0 1")); /// ``` fn row_map(&self, f: F) -> Matrix where @@ -2771,27 +2875,6 @@ impl FPMatrix for Matrix { // ============================================================================= // Linear Algebra // ============================================================================= - -/// Linear algebra trait -pub trait LinearAlgebra { - fn back_subs(&self, b: &Vec) -> Vec; - fn forward_subs(&self, b: &Vec) -> Vec; - fn lu(&self) -> PQLU; - fn waz(&self, d_form: Form) -> Option; - fn qr(&self) -> QR; - fn svd(&self) -> SVD; - #[cfg(feature = "O3")] - fn cholesky(&self, uplo: UPLO) -> Matrix; - fn rref(&self) -> Matrix; - fn det(&self) -> f64; - fn block(&self) -> (Matrix, Matrix, Matrix, Matrix); - fn inv(&self) -> Matrix; - fn pseudo_inv(&self) -> Matrix; - fn solve(&self, b: &Vec, sk: SolveKind) -> Vec; - fn solve_mat(&self, m: &Matrix, sk: SolveKind) -> Matrix; - fn is_symmetric(&self) -> bool; -} - pub fn diag(n: usize) -> Matrix { let mut v: Vec = vec![0f64; n * n]; for i in 0..n { @@ -2801,30 +2884,7 @@ pub fn diag(n: usize) -> Matrix { matrix(v, n, n, Row) } -/// Data structure for Complete Pivoting LU decomposition -/// -/// # Usage -/// ```rust -/// extern crate peroxide; -/// use peroxide::fuga::*; -/// -/// let a = ml_matrix("1 2;3 4"); -/// let pqlu = a.lu(); -/// let (p, q, l, u) = pqlu.extract(); -/// // p, q are permutations -/// // l, u are matrices -/// l.print(); // lower triangular -/// u.print(); // upper triangular -/// ``` -#[derive(Debug, Clone)] -pub struct PQLU { - pub p: Vec, - pub q: Vec, - pub l: Matrix, - pub u: Matrix, -} - -impl PQLU { +impl PQLU { pub fn extract(&self) -> (Vec, Vec, Matrix, Matrix) { ( self.p.clone(), @@ -2871,43 +2931,7 @@ impl PQLU { } } -#[derive(Debug, Clone)] -pub struct WAZD { - pub w: Matrix, - pub z: Matrix, - pub d: Matrix, -} - -#[derive(Debug, Copy, Clone)] -pub enum Form { - Diagonal, - Identity, -} - -#[derive(Debug, Clone)] -pub struct QR { - pub q: Matrix, - pub r: Matrix, -} - -impl QR { - pub fn q(&self) -> &Matrix { - &self.q - } - - pub fn r(&self) -> &Matrix { - &self.r - } -} - -#[derive(Debug, Clone)] -pub struct SVD { - pub s: Vec, - pub u: Matrix, - pub vt: Matrix, -} - -impl SVD { +impl SVD { pub fn u(&self) -> &Matrix { &self.u } @@ -2918,7 +2942,7 @@ impl SVD { pub fn s_mat(&self) -> Matrix { let mut mat = zeros(self.u.col, self.vt.row); - for i in 0 .. mat.row.min(mat.col) { + for i in 0..mat.row.min(mat.col) { mat[(i, i)] = self.s[i]; } mat @@ -2961,23 +2985,13 @@ impl SVD { } } - SVD { - s, - u, - vt, - } + SVD { s, u, vt } } } -#[derive(Debug, Copy, Clone)] -pub enum SolveKind { - LU, - WAZ, -} - -impl LinearAlgebra for Matrix { +impl LinearAlgebra for Matrix { /// Backward Substitution for Upper Triangular - fn back_subs(&self, b: &Vec) -> Vec { + fn back_subs(&self, b: &[f64]) -> Vec { let n = self.col; let mut y = vec![0f64; n]; y[n - 1] = b[n - 1] / self[(n - 1, n - 1)]; @@ -2992,7 +3006,7 @@ impl LinearAlgebra for Matrix { } /// Forward substitution for Lower Triangular - fn forward_subs(&self, b: &Vec) -> Vec { + fn forward_subs(&self, b: &[f64]) -> Vec { let n = self.col; let mut y = vec![0f64; n]; y[0] = b[0] / self[(0, 0)]; @@ -3014,27 +3028,23 @@ impl LinearAlgebra for Matrix { /// /// # Caution /// It returns `Option` - You should unwrap to obtain real value. - /// `PQLU` has four field - `p`, `q`, `l`, `u`. - /// `p`, `q` are permutations. - /// `l`, `u` are matrices. + /// - `PQLU` has four field - `p`, `q`, `l`, `u`. + /// - `p`, `q` are permutations. + /// - `l`, `u` are matrices. /// /// # Examples /// ``` - /// #[macro_use] - /// extern crate peroxide; /// use peroxide::fuga::*; /// - /// fn main() { - /// let a = matrix(vec![1,2,3,4], 2, 2, Row); - /// let pqlu = a.lu(); - /// let (p,q,l,u) = (pqlu.p, pqlu.q, pqlu.l, pqlu.u); - /// assert_eq!(p, vec![1]); // swap 0 & 1 (Row) - /// assert_eq!(q, vec![1]); // swap 0 & 1 (Col) - /// assert_eq!(l, matrix(c!(1,0,0.5,1),2,2,Row)); - /// assert_eq!(u, matrix(c!(4,3,0,-0.5),2,2,Row)); - /// } + /// let a = matrix(vec![1,2,3,4], 2, 2, Row); + /// let pqlu = a.lu(); + /// let (p,q,l,u) = (pqlu.p, pqlu.q, pqlu.l, pqlu.u); + /// assert_eq!(p, vec![1]); // swap 0 & 1 (Row) + /// assert_eq!(q, vec![1]); // swap 0 & 1 (Col) + /// assert_eq!(l, matrix(vec![1.0,0.0,0.5,1.0],2,2,Row)); + /// assert_eq!(u, matrix(vec![4.0,3.0,0.0,-0.5],2,2,Row)); /// ``` - fn lu(&self) -> PQLU { + fn lu(&self) -> PQLU { assert_eq!(self.col, self.row); let n = self.row; let len: usize = n * n; @@ -3066,7 +3076,7 @@ impl LinearAlgebra for Matrix { PQLU { p, q, l, u } } - fn waz(&self, d_form: Form) -> Option { + fn waz(&self, d_form: Form) -> Option> { match d_form { Form::Diagonal => { let n = self.row; @@ -3134,24 +3144,21 @@ impl LinearAlgebra for Matrix { /// /// # Example /// ``` - /// extern crate peroxide; /// use peroxide::fuga::*; /// - /// fn main() { - /// let a = ml_matrix("12 -51 4;6 167 -68; -4 24 -41"); - /// let qr = a.qr(); - /// let r = ml_matrix("-14 -21 14; 0 -175 70; 0 0 -35"); - /// #[cfg(feature="O3")] - /// { - /// assert_eq!(r, qr.r); - /// } - /// qr.r.print(); + /// let a = ml_matrix("12 -51 4;6 167 -68; -4 24 -41"); + /// let qr = a.qr(); + /// let r = ml_matrix("-14 -21 14; 0 -175 70; 0 0 -35"); + /// #[cfg(feature="O3")] + /// { + /// assert_eq!(r, qr.r); /// } + /// qr.r.print(); /// ``` #[allow(non_snake_case)] - fn qr(&self) -> QR { + fn qr(&self) -> QR { match () { - #[cfg(feature="O3")] + #[cfg(feature = "O3")] () => { let opt_dgeqrf = lapack_dgeqrf(self); match opt_dgeqrf { @@ -3159,10 +3166,7 @@ impl LinearAlgebra for Matrix { Some(dgeqrf) => { let q = dgeqrf.get_Q(); let r = dgeqrf.get_R(); - QR { - q, - r - } + QR { q, r } } } } @@ -3194,22 +3198,19 @@ impl LinearAlgebra for Matrix { /// /// # Examples /// ``` - /// extern crate peroxide; /// use peroxide::fuga::*; /// - /// fn main() { - /// let a = ml_matrix("3 2 2;2 3 -2"); - /// #[cfg(feature="O3")] - /// { - /// let svd = a.svd(); - /// assert!(eq_vec(&vec![5f64, 3f64], &svd.s, 1e-7)); - /// } - /// a.print(); + /// let a = ml_matrix("3 2 2;2 3 -2"); + /// #[cfg(feature="O3")] + /// { + /// let svd = a.svd(); + /// assert!(eq_vec(&vec![5f64, 3f64], &svd.s, 1e-7)); /// } + /// a.print(); /// ``` - fn svd(&self) -> SVD { + fn svd(&self) -> SVD { match () { - #[cfg(feature="O3")] + #[cfg(feature = "O3")] () => { let opt_dgesvd = lapack_dgesvd(self); match opt_dgesvd { @@ -3218,14 +3219,12 @@ impl LinearAlgebra for Matrix { SVD_STATUS::Diverge(i) => { panic!("Divergent occurs in SVD - {} iterations", i) } - SVD_STATUS::Success => { - SVD { - s: dgesvd.s, - u: dgesvd.u, - vt: dgesvd.vt, - } - } - } + SVD_STATUS::Success => SVD { + s: dgesvd.s, + u: dgesvd.u, + vt: dgesvd.vt, + }, + }, } } _ => { @@ -3238,21 +3237,18 @@ impl LinearAlgebra for Matrix { /// /// # Examples /// ``` - /// extern crate peroxide; /// use peroxide::fuga::*; /// - /// fn main() { - /// let a = ml_matrix("1 2;2 5"); - /// #[cfg(feature = "O3")] - /// { - /// let u = a.cholesky(Upper); - /// let l = a.cholesky(Lower); + /// let a = ml_matrix("1 2;2 5"); + /// #[cfg(feature = "O3")] + /// { + /// let u = a.cholesky(Upper); + /// let l = a.cholesky(Lower); /// - /// assert_eq!(u, ml_matrix("1 2;0 1")); - /// assert_eq!(l, ml_matrix("1 0;2 1")); - /// } - /// a.print(); + /// assert_eq!(u, ml_matrix("1 2;0 1")); + /// assert_eq!(l, ml_matrix("1 0;2 1")); /// } + /// a.print(); /// ``` #[cfg(feature = "O3")] fn cholesky(&self, uplo: UPLO) -> Matrix { @@ -3465,7 +3461,9 @@ impl LinearAlgebra for Matrix { None => panic!("Singular matrix (opt_dgrf)"), Some(dgrf) => match dgrf.status { LAPACK_STATUS::NonSingular => lapack_dgetri(&dgrf).unwrap(), - LAPACK_STATUS::Singular => panic!("Singular matrix (LAPACK_STATUS Singular)"), + LAPACK_STATUS::Singular => { + panic!("Singular matrix (LAPACK_STATUS Singular)") + } }, } } @@ -3500,13 +3498,13 @@ impl LinearAlgebra for Matrix { /// ``` fn pseudo_inv(&self) -> Self { match () { - #[cfg(feature="O3")] + #[cfg(feature = "O3")] () => { let svd = self.svd(); let row = svd.vt.row; let col = svd.u.row; let mut sp = zeros(row, col); - for i in 0 .. row.min(col) { + for i in 0..row.min(col) { sp[(i, i)] = 1f64 / svd.s[i]; } svd.vt.t() * sp * svd.u.t() @@ -3530,7 +3528,7 @@ impl LinearAlgebra for Matrix { /// /// * Biswa Nath Datta, *Numerical Linear Algebra and Applications, Second Edition* /// * Ke Chen, *Matrix Preconditioning Techniques and Applications*, Cambridge Monographs on Applied and Computational Mathematics - fn solve(&self, b: &Vec, sk: SolveKind) -> Vec { + fn solve(&self, b: &[f64], sk: SolveKind) -> Vec { match sk { #[cfg(feature = "O3")] SolveKind::LU => { @@ -3549,7 +3547,7 @@ impl LinearAlgebra for Matrix { SolveKind::LU => { let lu = self.lu(); let (p, q, l, u) = lu.extract(); - let mut v = b.clone(); + let mut v = b.to_vec(); v.swap_with_perm(&p.into_iter().enumerate().collect()); let z = l.forward_subs(&v); let mut y = u.back_subs(&z); @@ -3561,7 +3559,7 @@ impl LinearAlgebra for Matrix { None => panic!("Can't solve by WAZ with Singular matrix!"), Some(obj) => obj, }; - let x = &wazd.w.t() * b; + let x = &wazd.w.t() * &b.to_vec(); let x = &wazd.z * &x; x } @@ -3620,9 +3618,9 @@ impl LinearAlgebra for Matrix { return false; } - for i in 0 .. self.row { - for j in i .. self.col { - if !nearly_eq(self[(i,j)], self[(j,i)]) { + for i in 0..self.row { + for j in i..self.col { + if !nearly_eq(self[(i, j)], self[(j, i)]) { return false; } } @@ -3631,12 +3629,9 @@ impl LinearAlgebra for Matrix { } } -#[allow(non_snake_case)] -pub fn solve(A: &Matrix, b: &Matrix, sk: SolveKind) -> Matrix { - A.solve_mat(b, sk) -} - impl MutMatrix for Matrix { + type Scalar = f64; + unsafe fn col_mut(&mut self, idx: usize) -> Vec<*mut f64> { assert!(idx < self.col, "Index out of range"); match self.shape { @@ -3722,7 +3717,6 @@ impl Div for Matrix { } } - impl ExpLogOps for Matrix { type Float = f64; fn exp(&self) -> Self { @@ -4248,7 +4242,7 @@ pub enum POSITIVE_STATUS { #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum UPLO { Upper, - Lower + Lower, } /// Temporary data structure from `dgetrf` @@ -4279,7 +4273,7 @@ pub struct DGESVD { pub struct DPOTRF { pub fact_mat: Matrix, pub uplo: UPLO, - pub status: POSITIVE_STATUS + pub status: POSITIVE_STATUS, } ///// Temporary data structure from `dgeev` @@ -4473,7 +4467,22 @@ pub fn lapack_dgesvd(mat: &Matrix) -> Option { // Workspace query unsafe { - dgesvd(jobu, jobvt, m, n, &mut A.data, lda, &mut s, &mut u, ldu, &mut vt, ldvt, &mut work, lwork, &mut info); + dgesvd( + jobu, + jobvt, + m, + n, + &mut A.data, + lda, + &mut s, + &mut u, + ldu, + &mut vt, + ldvt, + &mut work, + lwork, + &mut info, + ); } let optimal_lwork = work[0] as usize; @@ -4532,35 +4541,23 @@ pub fn lapack_dpotrf(mat: &Matrix, UPLO: UPLO) -> Option { let mut info = 0i32; let uplo = match UPLO { UPLO::Upper => b'U', - UPLO::Lower => b'L' + UPLO::Lower => b'L', }; - unsafe { - dpotrf( - uplo, - N, - &mut A.data, - lda, - &mut info, - ) - } + unsafe { dpotrf(uplo, N, &mut A.data, lda, &mut info) } if info == 0 { - Some( - DPOTRF { - fact_mat: matrix(A.data, mat.row, mat.col, Col), - uplo: UPLO, - status: POSITIVE_STATUS::Success - } - ) + Some(DPOTRF { + fact_mat: matrix(A.data, mat.row, mat.col, Col), + uplo: UPLO, + status: POSITIVE_STATUS::Success, + }) } else if info > 0 { - Some( - DPOTRF { - fact_mat: matrix(A.data, mat.row, mat.col, Col), - uplo: UPLO, - status: POSITIVE_STATUS::Failed(info) - } - ) + Some(DPOTRF { + fact_mat: matrix(A.data, mat.row, mat.col, Col), + uplo: UPLO, + status: POSITIVE_STATUS::Failed(info), + }) } else { None } @@ -4674,8 +4671,8 @@ impl DPOTRF { let mat = &self.fact_mat; let n = mat.col; let mut result = matrix(vec![0f64; n.pow(2)], n, n, mat.shape); - for i in 0 .. n { - for j in i .. n { + for i in 0..n { + for j in i..n { result[(i, j)] = mat[(i, j)]; } } @@ -4691,8 +4688,8 @@ impl DPOTRF { let n = mat.col; let mut result = matrix(vec![0f64; n.pow(2)], n, n, mat.shape); - for i in 0 .. n { - for j in 0 .. i+1 { + for i in 0..n { + for j in 0..i + 1 { result[(i, j)] = mat[(i, j)]; } } diff --git a/src/structure/polynomial.rs b/src/structure/polynomial.rs index 07232363..422747be 100644 --- a/src/structure/polynomial.rs +++ b/src/structure/polynomial.rs @@ -6,8 +6,8 @@ use crate::util::useful::*; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use peroxide_num::PowOps; use crate::traits::fp::FPVector; +use peroxide_num::PowOps; use std::cmp::{max, min}; use std::fmt; use std::ops::{Add, Div, Mul, Neg, Sub}; @@ -51,7 +51,7 @@ impl fmt::Display for Polynomial { let coef_0 = self.coef[1]; if coef_1 == 1. { - result.push_str("x"); + result.push('x'); } else if coef_1 == -1. { result.push_str("-x"); } else { @@ -303,7 +303,7 @@ where type Output = Self; fn add(self, other: T) -> Self { let mut new_coef = self.coef.clone(); - new_coef[self.coef.len()-1] += other.into(); + new_coef[self.coef.len() - 1] += other.into(); Self::new(new_coef) } } @@ -322,7 +322,7 @@ where type Output = Self; fn sub(self, other: T) -> Self { let mut new_coef = self.coef.clone(); - new_coef[self.coef.len()-1] -= other.into(); + new_coef[self.coef.len() - 1] -= other.into(); Self::new(new_coef) } } @@ -555,7 +555,7 @@ pub fn lagrange_polynomial(node_x: Vec, node_y: Vec) -> Polynomial { let fixed_val = node_x[i]; let prod = node_y[i]; let mut id = poly(vec![1f64]); - + for j in 0..l { if j == i { continue; @@ -594,7 +594,7 @@ pub fn legendre_polynomial(n: usize) -> Polynomial { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SpecialKind { First, - Second + Second, } /// Chebyshev Polynomial @@ -609,7 +609,7 @@ pub fn chebyshev_polynomial(n: usize, kind: SpecialKind) -> Polynomial { 0 => prev, 1 => curr, _ => { - for _i in 1 .. n { + for _i in 1..n { std::mem::swap(&mut prev, &mut curr); curr = poly(vec![2f64, 0f64]) * prev.clone() - curr; } @@ -617,3 +617,49 @@ pub fn chebyshev_polynomial(n: usize, kind: SpecialKind) -> Polynomial { } } } + +/// Hermite Polynomial +/// +/// # Description +/// Generate `n`-th order Hermite polynomial. The physics convention +/// H_n(x) = (-1)^n e^{x^2}\frac{d^n}{dx^n}e^{-x^2} is used. +pub fn hermite_polynomial(n: usize) -> Polynomial { + let mut prev = Polynomial::new(vec![1f64]); // 1 + let mut curr = Polynomial::new(vec![2f64, 0f64]); // 2x + + match n { + 0 => prev, + 1 => curr, + _ => { + for idx in 1..n { + let k = idx as f64; + std::mem::swap(&mut prev, &mut curr); + curr = poly(vec![2f64, 0f64]) * prev.clone() - 2.0 * k * curr; + } + curr + } + } +} + +/// Bessel Polynomial +/// +/// # Description +/// Generate `n`-th order Bessel polynomial. Definition according to +/// Krall and Fink (1949). +pub fn bessel_polynomial(n: usize) -> Polynomial { + let mut prev = Polynomial::new(vec![1f64]); // 1 + let mut curr = Polynomial::new(vec![1f64, 1f64]); // x + 1 + + match n { + 0 => prev, + 1 => curr, + _ => { + for idx in 1..n { + let k = idx as f64; + std::mem::swap(&mut prev, &mut curr); + curr = (2.0 * k + 1.0) * poly(vec![1f64, 0f64]) * prev.clone() + curr; + } + curr + } + } +} diff --git a/src/structure/sparse.rs b/src/structure/sparse.rs index e640177f..d4ec390d 100644 --- a/src/structure/sparse.rs +++ b/src/structure/sparse.rs @@ -2,13 +2,14 @@ //! //! * Reference : Press, William H., and William T. Vetterling. *Numerical Recipes.* Cambridge: Cambridge Univ. Press, 2007. -use crate::structure::matrix::{Form, LinearAlgebra, Matrix, SolveKind, PQLU, QR, WAZD, SVD}; +use crate::structure::matrix::Matrix; use crate::traits::math::LinearOp; +use crate::traits::matrix::{Form, LinearAlgebra, SolveKind, PQLU, QR, SVD, WAZD}; //use crate::traits::math::{InnerProduct, LinearOp, Norm, Normed, Vector}; +#[cfg(feature = "O3")] +use crate::fuga::UPLO; use crate::util::non_macro::zeros; use std::ops::Mul; -#[cfg(feature="O3")] -use crate::fuga::UPLO; #[derive(Debug, Clone)] pub struct SPMatrix { @@ -138,32 +139,32 @@ impl LinearOp, Vec> for SPMatrix { /// Linear algebra for sparse matrix /// /// **Caution** : In every ops in this trait, there is converting process to dense matrix -impl LinearAlgebra for SPMatrix { - fn back_subs(&self, _b: &Vec) -> Vec { +impl LinearAlgebra for SPMatrix { + fn back_subs(&self, _b: &[f64]) -> Vec { unimplemented!() } - fn forward_subs(&self, _b: &Vec) -> Vec { + fn forward_subs(&self, _b: &[f64]) -> Vec { unimplemented!() } - fn lu(&self) -> PQLU { + fn lu(&self) -> PQLU { self.to_dense().lu() } - fn waz(&self, _d_form: Form) -> Option { + fn waz(&self, _d_form: Form) -> Option> { unimplemented!() } - fn qr(&self) -> QR { + fn qr(&self) -> QR { self.to_dense().qr() } - fn svd(&self) -> SVD { + fn svd(&self) -> SVD { unimplemented!() } - #[cfg(feature="O3")] + #[cfg(feature = "O3")] fn cholesky(&self, _uplo: UPLO) -> Matrix { unimplemented!() } @@ -188,7 +189,7 @@ impl LinearAlgebra for SPMatrix { self.to_dense().pseudo_inv() } - fn solve(&self, _b: &Vec, _sk: SolveKind) -> Vec { + fn solve(&self, _b: &[f64], _sk: SolveKind) -> Vec { unimplemented!() } diff --git a/src/structure/vector.rs b/src/structure/vector.rs index 0e977361..55a26c30 100644 --- a/src/structure/vector.rs +++ b/src/structure/vector.rs @@ -264,6 +264,11 @@ #[cfg(feature = "O3")] extern crate blas; +#[cfg(feature = "parallel")] +use crate::rayon::iter::{ + IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, + IntoParallelRefMutIterator, ParallelIterator, +}; #[cfg(feature = "O3")] use blas::{daxpy, ddot, dnrm2, idamax}; @@ -275,6 +280,12 @@ use crate::traits::{ mutable::MutFP, pointer::{Oxide, Redox, RedoxCommon}, }; +#[cfg(feature = "parallel")] +use crate::traits::{ + fp::ParallelFPVector, + math::{ParallelInnerProduct, ParallelNormed, ParallelVectorProduct}, + mutable::ParallelMutFP, +}; use std::cmp::min; impl FPVector for Vec { @@ -318,7 +329,7 @@ impl FPVector for Vec { fn reduce(&self, init: T, f: F) -> f64 where F: Fn(f64, f64) -> f64, - T: Into, + T: Into + Copy, { self.iter().fold(init.into(), |x, &y| f(x, y)) } @@ -409,6 +420,94 @@ impl FPVector for Vec { } } +#[cfg(feature = "parallel")] +impl ParallelFPVector for Vec { + type Scalar = f64; + + /// par_fmap for `Vec` + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::traits::fp::ParallelFPVector; + /// + /// fn main() { + /// let a = c!(1,2,3,4,5); + /// assert_eq!(a.par_fmap(|x| x*2f64), seq!(2,10,2)); + /// } + /// ``` + fn par_fmap(&self, f: F) -> Vec + where + F: Fn(f64) -> f64 + Sync + Send, + { + let mut v = self.clone(); + v.par_iter_mut().for_each(|x| *x = f(*x)); + v + } + + /// reduce for `Vec` + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::traits::fp::ParallelFPVector; + /// + /// fn main() { + /// let a = seq!(1,100,1); + /// assert_eq!(a.par_reduce(0, |x,y| x + y), 5050f64); + /// } + /// ``` + fn par_reduce(&self, _init: T, f: F) -> f64 + where + F: Fn(f64, f64) -> f64 + Send + Sync, + T: Into + Send + Sync + Copy, + { + self.par_iter() + .cloned() + .reduce_with(|x, y| f(x, y)) + .expect("Unable to perform parallel reduce operation") + } + + fn par_zip_with(&self, f: F, other: &Vec) -> Vec + where + F: Fn(f64, f64) -> f64 + Send + Sync, + { + self.par_iter() + .zip(other) + .map(|(x, y)| f(*x, *y)) + .collect::>() + } + + /// Filter for `Vec` + /// + /// # Examples + /// ``` + /// #[macro_use] + /// extern crate peroxide; + /// use peroxide::fuga::*; + /// use peroxide::traits::fp::ParallelFPVector; + /// + /// fn main() { + /// let a = c!(1,2,3,4,5); + /// let b = a.par_filter(|x| x > 3.); + /// assert_eq!(b, c!(4,5)); + /// } + /// ``` + fn par_filter(&self, f: F) -> Vec + where + F: Fn(f64) -> bool + Send + Sync, + { + self.clone() + .into_par_iter() + .filter(|x| f(*x)) + .collect::>() + } +} + /// Explicit version of `map` pub fn map(f: F, xs: &[T]) -> Vec where @@ -423,6 +522,18 @@ where result } +/// Explicit version of `map` in parallel +#[cfg(feature = "parallel")] +pub fn par_map(f: F, xs: &[T]) -> Vec +where + F: Fn(T) -> T + Send + Sync, + T: Default + Copy + Send + Sync, +{ + let mut result = vec![T::default(); xs.len()]; + result.par_iter_mut().for_each(|x| *x = f(*x)); + result +} + /// Explicit version of `reduce` pub fn reduce(f: F, init: T, xs: &[T]) -> T where @@ -450,6 +561,19 @@ where result } +/// Explicit version of `zip_with` in parallel +#[cfg(feature = "parallel")] +pub fn par_zip_with(f: F, xs: &[T], ys: &[T]) -> Vec +where + F: Fn(T, T) -> T + Send + Sync, + T: Default + Copy + Send + Sync, +{ + xs.par_iter() + .zip(ys.into_par_iter()) + .map(|(x, y)| f(*x, *y)) + .collect::>() +} + impl MutFP for Vec { type Scalar = f64; @@ -472,7 +596,30 @@ impl MutFP for Vec { } } +#[cfg(feature = "parallel")] +impl ParallelMutFP for Vec { + type Scalar = f64; + + fn par_mut_map(&mut self, f: F) + where + F: Fn(Self::Scalar) -> Self::Scalar + Send + Sync, + { + self.par_iter_mut().for_each(|x| *x = f(*x)); + } + + fn par_mut_zip_with(&mut self, f: F, other: &Self) + where + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar + Send + Sync, + { + self.par_iter_mut() + .zip(other.into_par_iter()) + .for_each(|(x, y)| *x = f(*x, *y)); + } +} + impl Algorithm for Vec { + type Scalar = f64; + /// Assign rank /// /// # Examples @@ -693,6 +840,52 @@ impl Normed for Vec { } } +#[cfg(feature = "parallel")] +impl ParallelNormed for Vec { + type UnsignedScalar = f64; + + fn par_norm(&self, kind: Norm) -> f64 { + match kind { + Norm::L1 => self.iter().map(|x| x.abs()).sum(), + Norm::L2 => { + #[cfg(feature = "O3")] + { + let n_i32 = self.len() as i32; + let res: f64; + unsafe { + res = dnrm2(n_i32, self, 1); + } + res + } + #[cfg(not(feature = "O3"))] + { + self.par_iter() + .map(|x| x.powi(2)) + .sum::() + .sqrt() + } + } + Norm::Lp(p) => { + assert!( + p >= 1f64, + "lp norm is only defined for p>=1, the given value was p={}", + p + ); + self.par_iter() + .map(|x| x.powf(p)) + .sum::() + .powf(1_f64 / p) + } + Norm::LInf => self + .par_iter() + .fold(|| 0f64, |x, y| x.max(y.abs())) + .reduce(|| 0f64, |acc1, acc2| acc1.max(acc2.abs())), + Norm::F => unimplemented!(), + Norm::Lpq(_, _) => unimplemented!(), + } + } +} + impl InnerProduct for Vec { fn dot(&self, rhs: &Self) -> f64 { #[cfg(feature = "O3")] @@ -713,6 +906,28 @@ impl InnerProduct for Vec { } } +#[cfg(feature = "parallel")] +impl ParallelInnerProduct for Vec { + fn par_dot(&self, rhs: &Self) -> f64 { + #[cfg(feature = "O3")] + { + let n_i32 = self.len() as i32; + let res: f64; + unsafe { + res = ddot(n_i32, self, 1, rhs, 1); + } + res + } + #[cfg(not(feature = "O3"))] + { + self.par_iter() + .zip(rhs.into_par_iter()) + .fold(|| 0_f64, |x, (y1, y2)| x + y1 * y2) + .sum::() + } + } +} + impl LinearOp, f64> for Vec { fn apply(&self, rhs: &Vec) -> f64 { self.dot(rhs) @@ -729,18 +944,22 @@ impl VectorProduct for Vec { fn cross(&self, other: &Self) -> Self { assert_eq!(self.len(), other.len()); // 2D cross product is ill-defined - if self.len() == 2 { - let mut v = vec![0f64; 1]; - v[0] = self[0] * other[1] - self[1] * other[0]; - v - } else if self.len() == 3 { - let mut v = vec![0f64; 3]; - v[0] = self[1] * other[2] - self[2] * other[1]; - v[1] = self[2] * other[0] - self[0] * other[2]; - v[2] = self[0] * other[1] - self[1] * other[0]; - v - } else { - panic!("Cross product can be defined only in 2 or 3 dimension") + match self.len() { + 2 => { + let mut v = vec![0f64; 1]; + v[0] = self[0] * other[1] - self[1] * other[0]; + v + } + 3 => { + let mut v = vec![0f64; 3]; + v[0] = self[1] * other[2] - self[2] * other[1]; + v[1] = self[2] * other[0] - self[0] * other[2]; + v[2] = self[0] * other[1] - self[1] * other[0]; + v + } + _ => { + panic!("Cross product can be defined only in 2 or 3 dimension") + } } } @@ -751,6 +970,34 @@ impl VectorProduct for Vec { } } +#[cfg(feature = "parallel")] +impl ParallelVectorProduct for Vec { + fn par_cross(&self, other: &Self) -> Self { + assert_eq!(self.len(), other.len()); + // 2D cross product is ill-defined + match self.len() { + 2 => { + let mut v = vec![0f64; 1]; + v[0] = self[0] * other[1] - self[1] * other[0]; + v + } + 3 => { + let v = (0..3) + .into_par_iter() + .map(|index| { + self[(index + 1) % 3] * other[(index + 2) % 3] + - self[(index + 2) % 3] * other[(index + 1) % 3] + }) + .collect::>(); + v + } + _ => { + panic!("Cross product can be defined only in 2 or 3 dimension") + } + } + } +} + // /// Convenient Vec Operations (No Clone, No Copy) // impl VecOps for Vec { // fn s_add(&self, scala: f64) -> Self { diff --git a/src/traits/fp.rs b/src/traits/fp.rs index 41f11463..2a2489ae 100644 --- a/src/traits/fp.rs +++ b/src/traits/fp.rs @@ -1,5 +1,3 @@ -use crate::structure::matrix::Matrix; - /// Functional Programming tools for Vector pub trait FPVector { type Scalar; @@ -10,7 +8,7 @@ pub trait FPVector { fn reduce(&self, init: T, f: F) -> Self::Scalar where F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, - T: Into; + T: Into + Copy; fn zip_with(&self, f: F, other: &Self) -> Self where F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar; @@ -23,38 +21,83 @@ pub trait FPVector { fn prod(&self) -> Self::Scalar; } -/// Functional Programming for Matrix +/// Functional Programming for Matrix and ComplexMatrix pub trait FPMatrix { - fn take_row(&self, n: usize) -> Matrix; - fn take_col(&self, n: usize) -> Matrix; - fn skip_row(&self, n: usize) -> Matrix; - fn skip_col(&self, n: usize) -> Matrix; - fn fmap(&self, f: F) -> Matrix + type Scalar; + + fn take_row(&self, n: usize) -> Self; + fn take_col(&self, n: usize) -> Self; + fn skip_row(&self, n: usize) -> Self; + fn skip_col(&self, n: usize) -> Self; + fn fmap(&self, f: F) -> Self where - F: Fn(f64) -> f64; - fn col_map(&self, f: F) -> Matrix + F: Fn(Self::Scalar) -> Self::Scalar; + fn col_map(&self, f: F) -> Self where - F: Fn(Vec) -> Vec; - fn row_map(&self, f: F) -> Matrix + F: Fn(Vec) -> Vec; + fn row_map(&self, f: F) -> Self where - F: Fn(Vec) -> Vec; + F: Fn(Vec) -> Vec; fn col_mut_map(&mut self, f: F) where - F: Fn(Vec) -> Vec; + F: Fn(Vec) -> Vec; fn row_mut_map(&mut self, f: F) where - F: Fn(Vec) -> Vec; - fn reduce(&self, init: T, f: F) -> f64 + F: Fn(Vec) -> Vec; + fn reduce(&self, init: T, f: F) -> Self::Scalar + where + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar, + T: Into; + fn zip_with(&self, f: F, other: &Self) -> Self + where + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar; + fn col_reduce(&self, f: F) -> Vec + where + F: Fn(Vec) -> Self::Scalar; + fn row_reduce(&self, f: F) -> Vec + where + F: Fn(Vec) -> Self::Scalar; +} + +/// Functional Programming tools for Vector in Parallel (Uses Rayon crate) +#[cfg(feature = "parallel")] +pub trait ParallelFPVector { + type Scalar; + + fn par_fmap(&self, f: F) -> Self + where + F: Fn(Self::Scalar) -> Self::Scalar + Send + Sync; + fn par_reduce(&self, init: T, f: F) -> Self::Scalar + where + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar + Send + Sync, + T: Into + Send + Sync + Copy; + fn par_zip_with(&self, f: F, other: &Self) -> Self + where + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar + Send + Sync; + fn par_filter(&self, f: F) -> Self + where + F: Fn(Self::Scalar) -> bool + Send + Sync; +} + +/// Functional Programming for Matrix in Parallel (Uses Rayon crate) +#[cfg(feature = "parallel")] +pub trait ParallelFPMatrix { + type Scalar; + + fn par_fmap(&self, f: F) -> Self + where + F: Fn(Self::Scalar) -> Self::Scalar + Send + Sync; + fn par_reduce(&self, init: T, f: F) -> Self::Scalar where - F: Fn(f64, f64) -> f64, - T: Into; - fn zip_with(&self, f: F, other: &Matrix) -> Matrix + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar + Send + Sync, + T: Into + Copy + Clone + Send + Sync; + fn par_zip_with(&self, f: F, other: &Self) -> Self where - F: Fn(f64, f64) -> f64; - fn col_reduce(&self, f: F) -> Vec + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar + Send + Sync; + fn par_col_reduce(&self, f: F) -> Vec where - F: Fn(Vec) -> f64; - fn row_reduce(&self, f: F) -> Vec + F: Fn(Vec) -> Self::Scalar + Send + Sync; + fn par_row_reduce(&self, f: F) -> Vec where - F: Fn(Vec) -> f64; + F: Fn(Vec) -> Self::Scalar + Send + Sync; } diff --git a/src/traits/general.rs b/src/traits/general.rs index 2737f97b..4712d304 100644 --- a/src/traits/general.rs +++ b/src/traits/general.rs @@ -1,10 +1,12 @@ /// Some algorithms for Vector pub trait Algorithm { + type Scalar; + fn rank(&self) -> Vec; - fn sign(&self) -> f64; + fn sign(&self) -> Self::Scalar; fn arg_max(&self) -> usize; fn arg_min(&self) -> usize; - fn max(&self) -> f64; - fn min(&self) -> f64; + fn max(&self) -> Self::Scalar; + fn min(&self) -> Self::Scalar; fn swap_with_perm(&mut self, p: &Vec<(usize, usize)>); } diff --git a/src/traits/math.rs b/src/traits/math.rs index 228aad9e..ce10bddb 100644 --- a/src/traits/math.rs +++ b/src/traits/math.rs @@ -60,8 +60,8 @@ pub trait VectorProduct: Vector { /// Matrix Products pub trait MatrixProduct { - fn kronecker(&self, other: &Self) -> Matrix; - fn hadamard(&self, other: &Self) -> Matrix; + fn kronecker(&self, other: &Self) -> Self; + fn hadamard(&self, other: &Self) -> Self; } // ============================================================================= @@ -97,3 +97,41 @@ impl Normed for f64 { self / self.abs() } } + +// ============================================================================= +// Implementation for parallel traits +// ============================================================================= + +/// Mathematical Vector in Parallel +#[cfg(feature = "parallel")] +pub trait ParallelVector { + type Scalar; + fn par_add_vec(&self, rhs: &Self) -> Self; + fn par_sub_vec(&self, rhs: &Self) -> Self; + fn par_mul_scalar(&self, rhs: Self::Scalar) -> Self; +} + +/// Normed Vector in Parallel +#[cfg(feature = "parallel")] +pub trait ParallelNormed: Vector { + type UnsignedScalar; + fn par_norm(&self, kind: Norm) -> Self::UnsignedScalar; +} + +/// Inner product Vector in Parallel +#[cfg(feature = "parallel")] +pub trait ParallelInnerProduct: ParallelNormed { + fn par_dot(&self, rhs: &Self) -> Self::Scalar; +} + +/// Matrix Products in Parallel +#[cfg(feature = "parallel")] +pub trait ParallelMatrixProduct { + fn par_hadamard(&self, other: &Self) -> Self; +} + +/// Vector Products in Parallel +#[cfg(feature = "parallel")] +pub trait ParallelVectorProduct: Vector { + fn par_cross(&self, other: &Self) -> Self; +} diff --git a/src/traits/matrix.rs b/src/traits/matrix.rs new file mode 100644 index 00000000..ce609bb0 --- /dev/null +++ b/src/traits/matrix.rs @@ -0,0 +1,123 @@ +use std::ops::{Index, IndexMut}; + +pub trait MatrixTrait: + Sized + Index<(usize, usize), Output = Self::Scalar> + IndexMut<(usize, usize)> +{ + type Scalar; + fn ptr(&self) -> *const Self::Scalar; + fn mut_ptr(&mut self) -> *mut Self::Scalar; + fn as_slice(&self) -> &[Self::Scalar]; + fn as_mut_slice(&mut self) -> &mut [Self::Scalar]; + fn change_shape(&self) -> Self; + fn change_shape_mut(&mut self); + fn spread(&self) -> String; + fn col(&self, index: usize) -> Vec; + fn row(&self, index: usize) -> Vec; + fn diag(&self) -> Vec; + fn transpose(&self) -> Self; + fn t(&self) -> Self { + self.transpose() + } + fn subs_col(&mut self, idx: usize, v: &[Self::Scalar]); + fn subs_row(&mut self, idx: usize, v: &[Self::Scalar]); + fn from_index(f: F, size: (usize, usize)) -> Self + where + F: Fn(usize, usize) -> G + Copy, + G: Into; + fn to_vec(&self) -> Vec>; + fn to_diag(&self) -> Self; + fn submat(&self, start: (usize, usize), end: (usize, usize)) -> Self; + fn subs_mat(&mut self, start: (usize, usize), end: (usize, usize), m: &Self); +} + +// ┌─────────────────────────────────────────────────────────┐ +// For Linear Algebra +// └─────────────────────────────────────────────────────────┘ +/// Linear algebra trait +pub trait LinearAlgebra { + fn back_subs(&self, b: &[M::Scalar]) -> Vec; + fn forward_subs(&self, b: &[M::Scalar]) -> Vec; + fn lu(&self) -> PQLU; + fn waz(&self, d_form: Form) -> Option>; + fn qr(&self) -> QR; + fn svd(&self) -> SVD; + #[cfg(feature = "O3")] + fn cholesky(&self, uplo: UPLO) -> M; + fn rref(&self) -> M; + fn det(&self) -> M::Scalar; + fn block(&self) -> (M, M, M, M); + fn inv(&self) -> M; + fn pseudo_inv(&self) -> M; + fn solve(&self, b: &[M::Scalar], sk: SolveKind) -> Vec; + fn solve_mat(&self, m: &M, sk: SolveKind) -> M; + fn is_symmetric(&self) -> bool; +} + +#[allow(non_snake_case)] +pub fn solve>(A: &M, b: &M, sk: SolveKind) -> M { + A.solve_mat(b, sk) +} + +/// Data structure for Complete Pivoting LU decomposition +/// +/// # Usage +/// ```rust +/// use peroxide::fuga::*; +/// +/// let a = ml_matrix("1 2;3 4"); +/// let pqlu = a.lu(); +/// let (p, q, l, u) = pqlu.extract(); +/// // p, q are permutations +/// // l, u are matrices +/// l.print(); // lower triangular +/// u.print(); // upper triangular +/// ``` +#[derive(Debug, Clone)] +pub struct PQLU { + pub p: Vec, + pub q: Vec, + pub l: M, + pub u: M, +} + +#[derive(Debug, Clone)] +pub struct WAZD { + pub w: M, + pub z: M, + pub d: M, +} + +#[derive(Debug, Clone)] +pub struct QR { + pub q: M, + pub r: M, +} + +#[derive(Debug, Copy, Clone)] +pub enum Form { + Diagonal, + Identity, +} + +#[derive(Debug, Copy, Clone)] +pub enum SolveKind { + LU, + WAZ, +} + +impl QR { + pub fn q(&self) -> &M { + &self.q + } + + pub fn r(&self) -> &M { + &self.r + } +} + +#[derive(Debug, Clone)] +pub struct SVD { + pub s: Vec, + pub u: M, + pub vt: M, +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 92b224a6..67a0d15b 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -1,9 +1,10 @@ +pub mod float; pub mod fp; pub mod general; pub mod math; +pub mod matrix; pub mod mutable; pub mod num; pub mod pointer; pub mod stable; pub mod sugar; -pub mod float; diff --git a/src/traits/mutable.rs b/src/traits/mutable.rs index 7b0932fc..45c473ee 100644 --- a/src/traits/mutable.rs +++ b/src/traits/mutable.rs @@ -11,8 +11,21 @@ pub trait MutFP { } pub trait MutMatrix { - unsafe fn col_mut(&mut self, idx: usize) -> Vec<*mut f64>; - unsafe fn row_mut(&mut self, idx: usize) -> Vec<*mut f64>; + type Scalar; + + unsafe fn col_mut(&mut self, idx: usize) -> Vec<*mut Self::Scalar>; + unsafe fn row_mut(&mut self, idx: usize) -> Vec<*mut Self::Scalar>; unsafe fn swap(&mut self, idx1: usize, idx2: usize, shape: Shape); unsafe fn swap_with_perm(&mut self, p: &Vec<(usize, usize)>, shape: Shape); } + +// Mutable trait for Vector in Parallel (Uses Rayon crate) +pub trait ParallelMutFP { + type Scalar; + fn par_mut_map(&mut self, f: F) + where + F: Fn(Self::Scalar) -> Self::Scalar + Send + Sync; + fn par_mut_zip_with(&mut self, f: F, other: &Self) + where + F: Fn(Self::Scalar, Self::Scalar) -> Self::Scalar + Send + Sync; +} diff --git a/src/traits/num.rs b/src/traits/num.rs index 77d3fa55..6b74e3cd 100644 --- a/src/traits/num.rs +++ b/src/traits/num.rs @@ -24,9 +24,9 @@ //! } //! ``` -use std::ops::{Neg, Add, Sub, Mul, Div}; use crate::structure::ad::AD; -use peroxide_num::{PowOps, TrigOps, ExpLogOps}; +use peroxide_num::{ExpLogOps, PowOps, TrigOps}; +use std::ops::{Add, Div, Mul, Neg, Sub}; pub trait Real: PowOps diff --git a/src/traits/pointer.rs b/src/traits/pointer.rs index 65fab349..8cc42e4d 100644 --- a/src/traits/pointer.rs +++ b/src/traits/pointer.rs @@ -43,12 +43,13 @@ //! ``` //! //! `ox()` and `red()` come from oxidation and reduction. +use crate::structure::ad::AD; use crate::structure::matrix::{Matrix, Shape}; use crate::structure::sparse::SPMatrix; -use crate::structure::ad::AD; use crate::traits::{ fp::FPVector, math::{LinearOp, Vector}, + matrix::MatrixTrait, }; use std::ops::{Add, Deref, Div, Mul, Sub}; @@ -78,7 +79,7 @@ impl RedoxCommon for Redox> { type ToRedox = Vec; fn from_vec(vec: Self::ToRedox) -> Self { Self { - data: Box::new(vec) + data: Box::new(vec), } } @@ -91,7 +92,7 @@ impl RedoxCommon for Redox> { type ToRedox = Vec; fn from_vec(vec: Self::ToRedox) -> Self { Self { - data: Box::new(vec) + data: Box::new(vec), } } diff --git a/src/traits/sugar.rs b/src/traits/sugar.rs index a51d9269..05cfc65e 100644 --- a/src/traits/sugar.rs +++ b/src/traits/sugar.rs @@ -1,15 +1,18 @@ -use crate::structure::matrix::{Matrix, Shape, matrix}; +use crate::structure::matrix::{matrix, Matrix, Shape}; use crate::traits::fp::FPVector; +use crate::traits::matrix::MatrixTrait; use crate::util::non_macro::zeros_shape; -use std::ops::{Add, Sub, Mul, Div}; +use std::ops::{Add, Div, Mul, Sub}; /// Syntactic sugar for Vector operations -pub trait VecOps: Sized + FPVector -where Self::Scalar: Copy + Clone - + Add - + Sub - + Mul - + Div +pub trait VecOps: Sized + FPVector +where + Self::Scalar: Copy + + Clone + + Add + + Sub + + Mul + + Div, { //type Scalar; fn add_v(&self, v: &Self) -> Self { @@ -449,7 +452,7 @@ impl ConvToMat for Vec { fn to_col(&self) -> Matrix { matrix(self.clone(), self.len(), 1, Shape::Col) } - + fn to_row(&self) -> Matrix { matrix(self.clone(), 1, self.len(), Shape::Row) } diff --git a/src/util/low_level.rs b/src/util/low_level.rs index 8b2a1f52..53fe5016 100644 --- a/src/util/low_level.rs +++ b/src/util/low_level.rs @@ -1,17 +1,26 @@ -pub unsafe fn copy_vec_ptr(dst: &mut Vec<*mut f64>, src: &Vec) { +pub unsafe fn copy_vec_ptr(dst: &mut Vec<*mut T>, src: &Vec) +where + T: Copy, +{ assert_eq!(dst.len(), src.len(), "Should use same length vectors"); for (&mut p, &s) in dst.iter_mut().zip(src) { *p = s; } } -pub unsafe fn swap_vec_ptr(lhs: &mut Vec<*mut f64>, rhs: &mut Vec<*mut f64>) { +pub unsafe fn swap_vec_ptr(lhs: &mut Vec<*mut T>, rhs: &mut Vec<*mut T>) +where + T: Copy, +{ assert_eq!(lhs.len(), rhs.len(), "Should use same length vectors"); for (&mut l, &mut r) in lhs.iter_mut().zip(rhs.iter_mut()) { std::ptr::swap(l, r); } } -pub unsafe fn ptr_to_vec(pv: &Vec<*const f64>) -> Vec { +pub unsafe fn ptr_to_vec(pv: &Vec<*const T>) -> Vec +where + T: Copy, +{ pv.iter().map(|&x| *x).collect() } diff --git a/src/util/non_macro.rs b/src/util/non_macro.rs index 1656d33f..e308a9b9 100644 --- a/src/util/non_macro.rs +++ b/src/util/non_macro.rs @@ -17,6 +17,7 @@ //! - linspace_with_precision //! - rand //! - rand_with_rng +//! - rand_with_dist //! //! # Numpy like non-macro functions //! @@ -36,7 +37,9 @@ use crate::structure::{ matrix::{matrix, Matrix, Shape}, }; use crate::traits::float::FloatWithPrecision; -use anyhow::{Result, bail}; +use crate::traits::matrix::MatrixTrait; +use anyhow::{bail, Result}; +use rand_distr::{Distribution, Uniform}; #[derive(Debug, Copy, Clone)] pub enum ConcatenateError { @@ -46,7 +49,10 @@ pub enum ConcatenateError { impl std::fmt::Display for ConcatenateError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { - ConcatenateError::DifferentLength => write!(f, "To concatenate, vectors or matrices must have the same length"), + ConcatenateError::DifferentLength => write!( + f, + "To concatenate, vectors or matrices must have the same length" + ), } } } @@ -62,7 +68,7 @@ impl std::fmt::Display for ConcatenateError { /// /// let a = seq(1, 10, 2); /// assert_eq!(a, vec![1f64,3f64,5f64,7f64,9f64]); -/// +/// /// let b = seq(1, 1, 1); /// assert_eq!(b, vec![1f64]); /// ``` @@ -248,11 +254,11 @@ pub fn eye_shape(n: usize, shape: Shape) -> Matrix { } /// MATLAB like linspace -/// +/// /// # Examples /// ``` /// use peroxide::fuga::*; -/// +/// /// let a = linspace(1, 10, 10); /// assert_eq!(a, seq(1,10,1)); /// assert_eq!(a.len(), 10); @@ -317,14 +323,8 @@ where /// /// Range = from 0 to 1 pub fn rand(r: usize, c: usize) -> Matrix { - let mut m = zeros(r, c); let mut rng = thread_rng(); - for i in 0..r { - for j in 0..c { - m[(i, j)] = rng.gen_range(0f64..=1f64); - } - } - m + rand_with_rng(r, c, &mut rng) } /// Rand matrix with specific rng @@ -333,20 +333,29 @@ pub fn rand(r: usize, c: usize) -> Matrix { /// /// Range = from 0 to 1 pub fn rand_with_rng(r: usize, c: usize, rng: &mut R) -> Matrix { - let mut m = zeros(r, c); - for i in 0..r { - for j in 0..c { - m[(i, j)] = rng.gen_range(0f64..=1f64); - } - } - m + let uniform = Uniform::new_inclusive(0f64, 1f64); + rand_with_dist(r, c, rng, uniform) +} + +/// Rand matrix with specific rng and distribution +/// +/// # Description +/// +/// Any range +pub fn rand_with_dist, R: Rng, D: Distribution>( + r: usize, + c: usize, + rng: &mut R, + dist: D, +) -> Matrix { + matrix(rng.sample_iter(dist).take(r * c).collect(), r, c, Row) } // ┌─────────────────────────────────────────────────────────┐ // Numpy like non-macro functions // └─────────────────────────────────────────────────────────┘ /// Numpy like logspace -/// +/// /// # Examples /// ``` /// use peroxide::fuga::*; @@ -370,7 +379,7 @@ where assert!(e >= s); - let step: f64 = if length > 1 { + let step: f64 = if length > 1 { (e - s) / (length as f64 - 1f64) } else { 0f64 diff --git a/src/util/plot.rs b/src/util/plot.rs index 7363c1f6..978ea7dd 100644 --- a/src/util/plot.rs +++ b/src/util/plot.rs @@ -487,14 +487,23 @@ impl Plot for Plot2D { let ylabel = self.ylabel.clone(); let legends = self.legends.clone(); let path = self.path.clone(); - let markers = self.markers.iter().map(|(i, x)| (i, format!("{}", x))).collect::>(); - let line_style = self.line_style.iter().map(|(i, x)| (i, format!("{}", x))).collect::>(); + let markers = self + .markers + .iter() + .map(|(i, x)| (i, format!("{}", x))) + .collect::>(); + let line_style = self + .line_style + .iter() + .map(|(i, x)| (i, format!("{}", x))) + .collect::>(); let color = self.color.clone(); let alpha = self.alpha.clone(); let plot_type = self.plot_type.clone(); // Global variables to plot - let globals = vec![("plt", py.import_bound("matplotlib.pyplot")?)].into_py_dict_bound(py); + let globals = + vec![("plt", py.import_bound("matplotlib.pyplot")?)].into_py_dict_bound(py); globals.as_gil_ref().set_item("x", x)?; globals.as_gil_ref().set_item("y", ys)?; globals.as_gil_ref().set_item("pair", pairs)?; @@ -515,16 +524,14 @@ impl Plot for Plot2D { // Plot Code let mut plot_string = match self.style { - PlotStyle::Default => { - "\ + PlotStyle::Default => "\ plt.rc(\"text\", usetex=True)\n\ - plt.rc(\"font\", family=\"serif\")\n".to_string() - } - PlotStyle::Science => { - "\ + plt.rc(\"font\", family=\"serif\")\n" + .to_string(), + PlotStyle::Science => "\ import scienceplots\n\ - plt.style.use(\"science\")\n".to_string() - } + plt.style.use(\"science\")\n" + .to_string(), _ => format!( "\ import scienceplots\n\ @@ -550,11 +557,15 @@ impl Plot for Plot2D { plot_string.push_str(&format!("plt.ylabel(r\"{}\")\n", y)[..]); } match self.xscale { - PlotScale::Linear => plot_string.push_str(&"plt.xscale(\"linear\")\n".to_string()[..]), + PlotScale::Linear => { + plot_string.push_str(&"plt.xscale(\"linear\")\n".to_string()[..]) + } PlotScale::Log => plot_string.push_str(&"plt.xscale(\"log\")\n".to_string()[..]), } match self.yscale { - PlotScale::Linear => plot_string.push_str(&"plt.yscale(\"linear\")\n".to_string()[..]), + PlotScale::Linear => { + plot_string.push_str(&"plt.yscale(\"linear\")\n".to_string()[..]) + } PlotScale::Log => plot_string.push_str(&"plt.yscale(\"log\")\n".to_string()[..]), } if self.xlim.is_some() { @@ -566,17 +577,20 @@ impl Plot for Plot2D { for i in 0..y_length { let mut inner_string = format!("x,y[{}]", i); - let is_corresponding_marker = !markers.is_empty() && (markers.iter().any(|(&j, _)| j == i)); + let is_corresponding_marker = + !markers.is_empty() && (markers.iter().any(|(&j, _)| j == i)); if is_corresponding_marker { let marker = markers.iter().find(|(&j, _)| j == i).unwrap().1.as_str(); inner_string.push_str(&format!(",marker=\"{}\"", marker)[..]); } - let is_corresponding_line_style = !line_style.is_empty() && (line_style.iter().any(|(&j, _)| j == i)); + let is_corresponding_line_style = + !line_style.is_empty() && (line_style.iter().any(|(&j, _)| j == i)); if is_corresponding_line_style { let style = line_style.iter().find(|(&j, _)| j == i).unwrap().1.as_str(); inner_string.push_str(&format!(",linestyle=\"{}\"", style)[..]); } - let is_corresponding_color = !color.is_empty() && (color.iter().any(|(j, _)| j == &i)); + let is_corresponding_color = + !color.is_empty() && (color.iter().any(|(j, _)| j == &i)); if is_corresponding_color { let color = color.iter().find(|(j, _)| j == &i).unwrap().1.as_str(); inner_string.push_str(&format!(",color=\"{}\"", color)[..]); @@ -584,12 +598,14 @@ impl Plot for Plot2D { if !legends.is_empty() { inner_string.push_str(&format!(",label=r\"{}\"", legends[i])[..]); } - let is_corresponding_alpha = !alpha.is_empty() && (alpha.iter().any(|(j, _)| j == &i)); + let is_corresponding_alpha = + !alpha.is_empty() && (alpha.iter().any(|(j, _)| j == &i)); if is_corresponding_alpha { let alpha = alpha.iter().find(|(j, _)| j == &i).unwrap().1; inner_string.push_str(&format!(",alpha={}", alpha)[..]); } - let is_corresponding_plot_type = !plot_type.is_empty() && (plot_type.iter().any(|(j, _)| j == &i)); + let is_corresponding_plot_type = + !plot_type.is_empty() && (plot_type.iter().any(|(j, _)| j == &i)); if is_corresponding_plot_type { let plot_type = plot_type.iter().find(|(j, _)| j == &i).unwrap().1; match plot_type { @@ -609,32 +625,56 @@ impl Plot for Plot2D { } for i in 0..pair_length { let mut inner_string = format!("pair[{}][0],pair[{}][1]", i, i); - let is_corresponding_marker = !markers.is_empty() && (markers.iter().any(|(&j, _)| j == (i + y_length))); + let is_corresponding_marker = + !markers.is_empty() && (markers.iter().any(|(&j, _)| j == (i + y_length))); if is_corresponding_marker { - let marker = markers.iter().find(|(&j, _)| j == (i + y_length)).unwrap().1.as_str(); + let marker = markers + .iter() + .find(|(&j, _)| j == (i + y_length)) + .unwrap() + .1 + .as_str(); inner_string.push_str(&format!(",marker=\"{}\"", marker)[..]); } - let is_corresponding_line_style = !line_style.is_empty() && (line_style.iter().any(|(&j, _)| j == (i + y_length))); + let is_corresponding_line_style = !line_style.is_empty() + && (line_style.iter().any(|(&j, _)| j == (i + y_length))); if is_corresponding_line_style { - let style = line_style.iter().find(|(&j, _)| j == (i + y_length)).unwrap().1.as_str(); + let style = line_style + .iter() + .find(|(&j, _)| j == (i + y_length)) + .unwrap() + .1 + .as_str(); inner_string.push_str(&format!(",linestyle=\"{}\"", style)[..]); } - let is_corresponding_color = !color.is_empty() && (color.iter().any(|(j, _)| j == &(i + y_length))); + let is_corresponding_color = + !color.is_empty() && (color.iter().any(|(j, _)| j == &(i + y_length))); if is_corresponding_color { - let color = color.iter().find(|(j, _)| j == &(i + y_length)).unwrap().1.as_str(); + let color = color + .iter() + .find(|(j, _)| j == &(i + y_length)) + .unwrap() + .1 + .as_str(); inner_string.push_str(&format!(",color=\"{}\"", color)[..]); } if !legends.is_empty() { inner_string.push_str(&format!(",label=r\"{}\"", legends[i + y_length])[..]); } - let is_corresponding_alpha = !alpha.is_empty() && (alpha.iter().any(|(j, _)| j == &(i + y_length))); + let is_corresponding_alpha = + !alpha.is_empty() && (alpha.iter().any(|(j, _)| j == &(i + y_length))); if is_corresponding_alpha { let alpha = alpha.iter().find(|(j, _)| j == &(i + y_length)).unwrap().1; inner_string.push_str(&format!(",alpha={}", alpha)[..]); } - let is_corresponding_plot_type = !plot_type.is_empty() && (plot_type.iter().any(|(j, _)| j == &(i + y_length))); + let is_corresponding_plot_type = + !plot_type.is_empty() && (plot_type.iter().any(|(j, _)| j == &(i + y_length))); if is_corresponding_plot_type { - let plot_type = plot_type.iter().find(|(j, _)| j == &(i + y_length)).unwrap().1; + let plot_type = plot_type + .iter() + .find(|(j, _)| j == &(i + y_length)) + .unwrap() + .1; match plot_type { PlotType::Scatter => { plot_string.push_str(&format!("plt.scatter({})\n", inner_string)[..]); @@ -656,7 +696,8 @@ impl Plot for Plot2D { } if self.tight { - plot_string.push_str(&format!("plt.savefig(pa, dpi={}, bbox_inches='tight')", dpi)[..]); + plot_string + .push_str(&format!("plt.savefig(pa, dpi={}, bbox_inches='tight')", dpi)[..]); } else { plot_string.push_str(&format!("plt.savefig(pa, dpi={})", dpi)[..]); } diff --git a/src/util/print.rs b/src/util/print.rs index e9038682..68ac7d63 100644 --- a/src/util/print.rs +++ b/src/util/print.rs @@ -5,10 +5,10 @@ use crate::statistics::stat::ConfusionMatrix; #[allow(unused_imports)] use crate::structure::{ ad::AD, + dataframe::{DType, DTypeArray, DataFrame, Scalar, Series}, matrix::Matrix, multinomial::Multinomial, polynomial::Polynomial, - dataframe::{DataFrame, DTypeArray, Series, Scalar, DType}, }; use rand::distributions::uniform::SampleUniform; use std::fmt::{Debug, LowerExp, UpperExp}; @@ -189,14 +189,10 @@ macro_rules! format_float_vec { ($self:expr) => {{ let mut result = String::new(); result.push_str("["); - for i in 0 .. $self.len() { + for i in 0..$self.len() { let st1 = $self[i].fmt_lower_exp(2); let st2 = $self[i].to_string(); - let st = if st1.len() < st2.len() { - st1 - } else { - st2 - }; + let st = if st1.len() < st2.len() { st1 } else { st2 }; result.push_str(&st); if i == $self.len() - 1 { break; @@ -426,9 +422,9 @@ impl Printable for ConfusionMatrix { } /// Format float number into lower exponent notation with '+' sign -/// +/// /// # Example -/// +/// /// ```rust /// use peroxide::fuga::*; /// @@ -439,7 +435,7 @@ impl Printable for ConfusionMatrix { /// ``` pub trait LowerExpWithPlus: LowerExp { fn fmt_lower_exp(&self, precision: usize) -> String { - let mut s = format!("{:.p$e}", self, p=precision); + let mut s = format!("{:.p$e}", self, p = precision); let s_old = s.clone(); let mut e = s.split_off(s.find('e').unwrap()); if e.starts_with("e-") { @@ -455,9 +451,9 @@ impl LowerExpWithPlus for f32 {} impl LowerExpWithPlus for f64 {} /// Format float number into upper exponent notation with '+' sign -/// +/// /// # Example -/// +/// /// ```rust /// use peroxide::fuga::*; /// @@ -468,7 +464,7 @@ impl LowerExpWithPlus for f64 {} /// ``` pub trait UpperExpWithPlus: UpperExp { fn fmt_upper_exp(&self, precision: usize) -> String { - let mut s = format!("{:.p$E}", self, p=precision); + let mut s = format!("{:.p$E}", self, p = precision); let s_old = s.clone(); let mut e = s.split_off(s.find('E').unwrap()); if e.starts_with("E-") { diff --git a/src/util/useful.rs b/src/util/useful.rs index b2a5df7f..e5da4a8a 100644 --- a/src/util/useful.rs +++ b/src/util/useful.rs @@ -124,29 +124,29 @@ pub fn eq_vec(x: &[f64], y: &[f64], tol: f64) -> bool { // Vec of Tuples // ============================================================================= /// Auto-zip -/// +/// /// # Examples /// ``` /// extern crate peroxide; /// use peroxide::fuga::*; -/// +/// /// let a = vec![1, 2, 3]; /// let a_zipped = auto_zip(&a); /// assert_eq!(a_zipped, vec![(1, 2), (2, 3)]); /// ``` pub fn auto_zip(x: &Vec) -> Vec<(T, T)> { - let x_head = x[0 .. x.len() - 1].to_vec(); - let x_tail = x[1 .. x.len()].to_vec(); + let x_head = x[0..x.len() - 1].to_vec(); + let x_tail = x[1..x.len()].to_vec(); x_head.into_iter().zip(x_tail).collect() } /// Find the index of interval of x -/// +/// /// # Examples /// ``` /// extern crate peroxide; /// use peroxide::fuga::*; -/// +/// /// let x = vec![ /// (0, 5), /// (5, 7), @@ -155,7 +155,7 @@ pub fn auto_zip(x: &Vec) -> Vec<(T, T)> { /// (15, 20), /// (20, 30) /// ]; -/// +/// /// assert_eq!(find_interval(&x, 11), 3); /// ``` pub fn find_interval(sorted_intervals: &Vec<(T, T)>, x: T) -> usize { @@ -163,8 +163,14 @@ pub fn find_interval(sorted_intervals: &Vec<(T, T)>, let mut j = sorted_intervals.len() - 1; // Check range - assert!(x >= sorted_intervals[0].0, "x is smaller than the smallest interval"); - assert!(x <= sorted_intervals[sorted_intervals.len() - 1].1, "x is larger than the largest interval"); + assert!( + x >= sorted_intervals[0].0, + "x is smaller than the smallest interval" + ); + assert!( + x <= sorted_intervals[sorted_intervals.len() - 1].1, + "x is larger than the largest interval" + ); while i <= j { let mid = (i + j) / 2; @@ -180,22 +186,22 @@ pub fn find_interval(sorted_intervals: &Vec<(T, T)>, } /// Generate Range of Intervals -/// +/// /// # Examples /// ``` /// extern crate peroxide; /// use peroxide::fuga::*; /// use std::ops::Range; -/// +/// /// let x = vec![1, 2, 3, 4]; /// let r = gen_range(&x); -/// +/// /// let answer = vec![ /// Range { start: 1, end: 2 }, /// Range { start: 2, end: 3 }, /// Range { start: 3, end: 4 }, /// ]; -/// +/// /// assert_eq!(r, answer); /// ``` pub fn gen_range(x: &[T]) -> Vec> { @@ -210,31 +216,37 @@ pub fn gen_range(x: &[T]) -> Vec> { } /// Generate and Zip Range of Intervals -/// +/// /// # Examples /// ``` /// extern crate peroxide; /// use peroxide::fuga::*; /// use std::ops::Range; -/// +/// /// let x: Vec = vec![1, 2, 3, 4]; /// let y: Vec = x.iter().map(|&t| t.pow(2)).collect(); /// let r = zip_range(&x, &y); -/// +/// /// let answer = vec![ /// (Range { start: 1, end: 2 }, 1), /// (Range { start: 2, end: 3 }, 4), /// (Range { start: 3, end: 4 }, 9), /// ]; -/// +/// /// assert_eq!(r, answer); /// ``` pub fn zip_range(x: &[T], y: &[U]) -> Vec<(Range, U)> { - y[0 .. x.len() - 1].iter() + y[0..x.len() - 1] + .iter() .enumerate() - .map(|(i, yi)| (Range { - start: x[i].clone(), - end: x[i + 1].clone(), - }, yi.clone())) + .map(|(i, yi)| { + ( + Range { + start: x[i].clone(), + end: x[i + 1].clone(), + }, + yi.clone(), + ) + }) .collect() } diff --git a/tests/blas_test.rs b/tests/blas_test.rs index 5401eeac..59c2767e 100644 --- a/tests/blas_test.rs +++ b/tests/blas_test.rs @@ -3,7 +3,7 @@ extern crate peroxide; use peroxide::fuga::*; #[test] -#[cfg(feature = "native")] +#[cfg(feature = "O3")] fn daxpy_test() { let a = ml_matrix("1 2; 3 4"); let b = matrix(vec![1, 3, 2, 4], 2, 2, Col); @@ -14,7 +14,7 @@ fn daxpy_test() { } #[test] -#[cfg(feature = "native")] +#[cfg(feature = "O3")] fn dgemv_test() { let a = ml_matrix("1 2;3 4"); let b = c![1, 2]; @@ -24,7 +24,7 @@ fn dgemv_test() { } #[test] -#[cfg(feature = "native")] +#[cfg(feature = "O3")] fn dgemm_test() { // 2x2 let a = ml_matrix("1 2;3 4"); diff --git a/tests/complex_matrix.rs b/tests/complex_matrix.rs new file mode 100644 index 00000000..113c413e --- /dev/null +++ b/tests/complex_matrix.rs @@ -0,0 +1,23 @@ +extern crate peroxide; +#[allow(unused_imports)] +use peroxide::fuga::*; + +#[cfg(feature = "complex")] +#[test] +fn test_seq() { + use num_complex::Complex64; + use peroxide::complex::matrix::ComplexMatrix; + + let v1 = ComplexMatrix { + data: vec![ + Complex64::new(1f64, 1f64), + Complex64::new(2f64, 2f64), + Complex64::new(3f64, 3f64), + Complex64::new(4f64, 4f64), + ], + row: 2, + col: 2, + shape: Row, + }; + assert_eq!(v1.data[0], Complex64::new(1f64, 1f64)); +} diff --git a/tests/dataframe/dataframe.rs b/tests/dataframe/dataframe.rs index 6f9c28bf..4417dc85 100644 --- a/tests/dataframe/dataframe.rs +++ b/tests/dataframe/dataframe.rs @@ -4,8 +4,8 @@ use peroxide::fuga::*; #[test] fn test_type_cast() { let mut a = DataFrame::new(vec![]); - a.push("x", Series::new(vec![1,2,3,4])); - a.push("y", Series::new(vec![true,false,false,true])); + a.push("x", Series::new(vec![1, 2, 3, 4])); + a.push("y", Series::new(vec![true, false, false, true])); let mut b = DataFrame::new(vec![]); b.push("x", Series::new(vec![1usize, 2, 3, 4])); diff --git a/tests/dataframe/mod.rs b/tests/dataframe/mod.rs index 4f4cc067..123af7ed 100644 --- a/tests/dataframe/mod.rs +++ b/tests/dataframe/mod.rs @@ -1,3 +1,3 @@ -pub mod series; pub mod dataframe; -pub mod print; \ No newline at end of file +pub mod print; +pub mod series; diff --git a/tests/dataframe/print.rs b/tests/dataframe/print.rs index 7eb0316b..065f5681 100644 --- a/tests/dataframe/print.rs +++ b/tests/dataframe/print.rs @@ -9,4 +9,4 @@ fn test_print() { df.push("z", Series::new(seq(0, 10, 0.01))); df.print(); -} \ No newline at end of file +} diff --git a/tests/dataframe/series.rs b/tests/dataframe/series.rs index 2e92bd17..ec91c086 100644 --- a/tests/dataframe/series.rs +++ b/tests/dataframe/series.rs @@ -3,16 +3,16 @@ use peroxide::fuga::*; #[test] fn test_map() { - let a = Series::new(vec![1,2,3,4]); + let a = Series::new(vec![1, 2, 3, 4]); let b = a.map(|x: i32| x + 1); - assert_eq!(b, Series::new(vec![2,3,4,5])); + assert_eq!(b, Series::new(vec![2, 3, 4, 5])); } #[test] fn test_mut_map() { - let mut a = Series::new(vec![1,2,3,4]); + let mut a = Series::new(vec![1, 2, 3, 4]); a.mut_map(|x: &mut i32| *x += 1); - assert_eq!(a, Series::new(vec![2,3,4,5])); + assert_eq!(a, Series::new(vec![2, 3, 4, 5])); } diff --git a/tests/integrated.rs b/tests/integrated.rs index 79e67589..4e969ba8 100644 --- a/tests/integrated.rs +++ b/tests/integrated.rs @@ -3,5 +3,5 @@ extern crate peroxide; #[allow(unused_imports)] use peroxide::fuga::*; -mod o3; mod dataframe; +mod o3; diff --git a/tests/numerical.rs b/tests/numerical.rs index fd6091c2..f0aec082 100644 --- a/tests/numerical.rs +++ b/tests/numerical.rs @@ -12,7 +12,7 @@ fn f(x: f64) -> f64 { } #[test] -fn test_cubic_spline_initialization() -> Result<(), Box>{ +fn test_cubic_spline_initialization() -> Result<(), Box> { let mut vx = Vec::new(); let mut vy = Vec::new(); for i in 0..11 { @@ -34,7 +34,7 @@ fn test_cubic_spline_initialization() -> Result<(), Box>{ } #[test] -fn test_cubic_spline_extension() -> Result<(), Box>{ +fn test_cubic_spline_extension() -> Result<(), Box> { let mut vx = Vec::new(); let mut vy = Vec::new(); for i in 0..11 { diff --git a/tests/o3/lapack.rs b/tests/o3/lapack.rs index d65a9360..c50e869a 100644 --- a/tests/o3/lapack.rs +++ b/tests/o3/lapack.rs @@ -13,20 +13,128 @@ pub fn test_apply() { #[cfg(feature = "O3")] #[test] pub fn test_dpotrf() { - let a = py_matrix( - vec![ - vec![2.9821852954666 , 2.680083137271475, 2.317961273223499, 2.134665492195094, 3.026598331879419, 2.283461999828011, 3.727890627691731, 1.948290349481972, 2.509358003244204, 2.354788397327008], - vec![2.680083137271475, 4.018906407367834, 2.341820593934314, 2.830765880629095, 3.552151618608445, 2.460655598487766, 3.630225204862198, 1.796569139726129, 2.846699793116624, 2.774282835105069], - vec![2.317961273223499, 2.341820593934314, 2.038910554565652, 1.966181894408401, 2.536384937997141, 1.886915794296248, 3.136970841370761, 1.905232489093743, 2.01726450417587 , 2.238391500871792], - vec![2.134665492195094, 2.830765880629095, 1.966181894408401, 3.301422256938514, 2.794302738298506, 2.205849011689008, 2.679880725689802, 1.555759606416891, 2.475715452431455, 2.116394736527704], - vec![3.026598331879419, 3.552151618608445, 2.536384937997141, 2.794302738298506, 4.337375745514422, 3.604658556851256, 4.143687579387594, 2.385563118734139, 3.321157832605267, 2.949560656237908], - vec![2.283461999828011, 2.460655598487766, 1.886915794296248, 2.205849011689008, 3.604658556851256, 3.413575093545912, 3.168622634363222, 1.907039710137486, 2.698561362072113, 2.249353149769269], - vec![3.727890627691731, 3.630225204862198, 3.136970841370761, 2.679880725689802, 4.143687579387594, 3.168622634363222, 5.142703863442649, 2.977549597798733, 3.210043437463944, 3.385961680761656], - vec![1.948290349481972, 1.796569139726129, 1.905232489093743, 1.555759606416891, 2.385563118734139, 1.907039710137486, 2.977549597798733, 2.230209604622137, 1.742552591231192, 2.252632855204552], - vec![2.509358003244204, 2.846699793116624, 2.01726450417587 , 2.475715452431455, 3.321157832605267, 2.698561362072113, 3.210043437463944, 1.742552591231192, 3.202516498869207, 2.228297655595811], - vec![2.354788397327008, 2.774282835105069, 2.238391500871792, 2.116394736527704, 2.949560656237908, 2.249353149769269, 3.385961680761656, 2.252632855204552, 2.228297655595811, 2.784914722669765] - ] - ); + let a = py_matrix(vec![ + vec![ + 2.9821852954666, + 2.680083137271475, + 2.317961273223499, + 2.134665492195094, + 3.026598331879419, + 2.283461999828011, + 3.727890627691731, + 1.948290349481972, + 2.509358003244204, + 2.354788397327008, + ], + vec![ + 2.680083137271475, + 4.018906407367834, + 2.341820593934314, + 2.830765880629095, + 3.552151618608445, + 2.460655598487766, + 3.630225204862198, + 1.796569139726129, + 2.846699793116624, + 2.774282835105069, + ], + vec![ + 2.317961273223499, + 2.341820593934314, + 2.038910554565652, + 1.966181894408401, + 2.536384937997141, + 1.886915794296248, + 3.136970841370761, + 1.905232489093743, + 2.01726450417587, + 2.238391500871792, + ], + vec![ + 2.134665492195094, + 2.830765880629095, + 1.966181894408401, + 3.301422256938514, + 2.794302738298506, + 2.205849011689008, + 2.679880725689802, + 1.555759606416891, + 2.475715452431455, + 2.116394736527704, + ], + vec![ + 3.026598331879419, + 3.552151618608445, + 2.536384937997141, + 2.794302738298506, + 4.337375745514422, + 3.604658556851256, + 4.143687579387594, + 2.385563118734139, + 3.321157832605267, + 2.949560656237908, + ], + vec![ + 2.283461999828011, + 2.460655598487766, + 1.886915794296248, + 2.205849011689008, + 3.604658556851256, + 3.413575093545912, + 3.168622634363222, + 1.907039710137486, + 2.698561362072113, + 2.249353149769269, + ], + vec![ + 3.727890627691731, + 3.630225204862198, + 3.136970841370761, + 2.679880725689802, + 4.143687579387594, + 3.168622634363222, + 5.142703863442649, + 2.977549597798733, + 3.210043437463944, + 3.385961680761656, + ], + vec![ + 1.948290349481972, + 1.796569139726129, + 1.905232489093743, + 1.555759606416891, + 2.385563118734139, + 1.907039710137486, + 2.977549597798733, + 2.230209604622137, + 1.742552591231192, + 2.252632855204552, + ], + vec![ + 2.509358003244204, + 2.846699793116624, + 2.01726450417587, + 2.475715452431455, + 3.321157832605267, + 2.698561362072113, + 3.210043437463944, + 1.742552591231192, + 3.202516498869207, + 2.228297655595811, + ], + vec![ + 2.354788397327008, + 2.774282835105069, + 2.238391500871792, + 2.116394736527704, + 2.949560656237908, + 2.249353149769269, + 3.385961680761656, + 2.252632855204552, + 2.228297655595811, + 2.784914722669765, + ], + ]); let dpotrf_u = lapack_dpotrf(&a, UPLO::Upper).unwrap(); let dpotrf_l = lapack_dpotrf(&a, UPLO::Lower).unwrap(); @@ -34,35 +142,229 @@ pub fn test_dpotrf() { let u = dpotrf_u.get_U().unwrap(); let l = dpotrf_l.get_L().unwrap(); - let scipy_u = py_matrix( - vec![ - vec![ 1.726900488003463, 1.551961537963327, 1.34226684706273 , 1.236125362766596, 1.752618841041957, 1.322289278213136, 2.158717687318329, 1.128200705840593, 1.453099365410088, 1.363592409455782], - vec![ 0. , 1.268984551541246, 0.203843359082252, 0.718958209858647, 0.65576415850829 , 0.321921568031328, 0.220631829192057, 0.035969734306862, 0.466156555210059, 0.518556243550586], - vec![ 0. , 0. , 0.442355231459957, 0.362642811042644, 0.113550376983589, 0.104958994115387, 0.439514016124092, 0.867072131324973, -0.063762143110917, 0.683573627458849], - vec![ 0. , 0. , 0. , 1.060662825095517, 0.108612163548495, 0.284560665177766, -0.289042488015237, -0.168890722850109, 0.346460971292225, -0.179029330692296], - vec![ 0. , 0. , 0. , 0. , 0.90054762326669 , 1.14736846650923 , 0.218847271328198, 0.33819073440549 , 0.486759471799237, 0.17930981650712 ], - vec![ 0. , 0. , 0. , 0. , 0. , 0.391212348671252, 0.072001696614059, -0.069844847295321, -0.059587640909243, 0.135011494209335], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0.32274897849312 , 0.109238423598569, -0.011482954266517, -0.226830086097441], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.211084069199511, 0.154785258348754, 0.218861000711332], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.696448259994717, -0.007136319580541], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.086731850555121] - ] - ); + let scipy_u = py_matrix(vec![ + vec![ + 1.726900488003463, + 1.551961537963327, + 1.34226684706273, + 1.236125362766596, + 1.752618841041957, + 1.322289278213136, + 2.158717687318329, + 1.128200705840593, + 1.453099365410088, + 1.363592409455782, + ], + vec![ + 0., + 1.268984551541246, + 0.203843359082252, + 0.718958209858647, + 0.65576415850829, + 0.321921568031328, + 0.220631829192057, + 0.035969734306862, + 0.466156555210059, + 0.518556243550586, + ], + vec![ + 0., + 0., + 0.442355231459957, + 0.362642811042644, + 0.113550376983589, + 0.104958994115387, + 0.439514016124092, + 0.867072131324973, + -0.063762143110917, + 0.683573627458849, + ], + vec![ + 0., + 0., + 0., + 1.060662825095517, + 0.108612163548495, + 0.284560665177766, + -0.289042488015237, + -0.168890722850109, + 0.346460971292225, + -0.179029330692296, + ], + vec![ + 0., + 0., + 0., + 0., + 0.90054762326669, + 1.14736846650923, + 0.218847271328198, + 0.33819073440549, + 0.486759471799237, + 0.17930981650712, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0.391212348671252, + 0.072001696614059, + -0.069844847295321, + -0.059587640909243, + 0.135011494209335, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0.32274897849312, + 0.109238423598569, + -0.011482954266517, + -0.226830086097441, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0.211084069199511, + 0.154785258348754, + 0.218861000711332, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0.696448259994717, + -0.007136319580541, + ], + vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0.086731850555121], + ]); - let scipy_l = py_matrix( - vec![ - vec![ 1.726900488003463, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.551961537963327, 1.268984551541246, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.34226684706273 , 0.203843359082252, 0.442355231459957, 0. , 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.236125362766596, 0.718958209858647, 0.362642811042644, 1.060662825095517, 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.752618841041957, 0.65576415850829 , 0.113550376983589, 0.108612163548495, 0.90054762326669 , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.322289278213136, 0.321921568031328, 0.104958994115387, 0.284560665177766, 1.14736846650923 , 0.391212348671252, 0. , 0. , 0. , 0. ], - vec![ 2.158717687318329, 0.220631829192057, 0.439514016124092, -0.289042488015237, 0.218847271328198, 0.072001696614058, 0.32274897849312 , 0. , 0. , 0. ], - vec![ 1.128200705840593, 0.035969734306862, 0.867072131324973, -0.168890722850108, 0.33819073440549 , -0.069844847295322, 0.109238423598569, 0.211084069199512, 0. , 0. ], - vec![ 1.453099365410088, 0.466156555210059, -0.063762143110917, 0.346460971292225, 0.486759471799238, -0.059587640909244, -0.011482954266516, 0.154785258348753, 0.696448259994717, 0. ], - vec![ 1.363592409455782, 0.518556243550586, 0.683573627458849, -0.179029330692296, 0.17930981650712 , 0.135011494209336, -0.226830086097441, 0.218861000711332, -0.00713631958054 , 0.086731850555118] - ] - ); + let scipy_l = py_matrix(vec![ + vec![1.726900488003463, 0., 0., 0., 0., 0., 0., 0., 0., 0.], + vec![ + 1.551961537963327, + 1.268984551541246, + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.34226684706273, + 0.203843359082252, + 0.442355231459957, + 0., + 0., + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.236125362766596, + 0.718958209858647, + 0.362642811042644, + 1.060662825095517, + 0., + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.752618841041957, + 0.65576415850829, + 0.113550376983589, + 0.108612163548495, + 0.90054762326669, + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.322289278213136, + 0.321921568031328, + 0.104958994115387, + 0.284560665177766, + 1.14736846650923, + 0.391212348671252, + 0., + 0., + 0., + 0., + ], + vec![ + 2.158717687318329, + 0.220631829192057, + 0.439514016124092, + -0.289042488015237, + 0.218847271328198, + 0.072001696614058, + 0.32274897849312, + 0., + 0., + 0., + ], + vec![ + 1.128200705840593, + 0.035969734306862, + 0.867072131324973, + -0.168890722850108, + 0.33819073440549, + -0.069844847295322, + 0.109238423598569, + 0.211084069199512, + 0., + 0., + ], + vec![ + 1.453099365410088, + 0.466156555210059, + -0.063762143110917, + 0.346460971292225, + 0.486759471799238, + -0.059587640909244, + -0.011482954266516, + 0.154785258348753, + 0.696448259994717, + 0., + ], + vec![ + 1.363592409455782, + 0.518556243550586, + 0.683573627458849, + -0.179029330692296, + 0.17930981650712, + 0.135011494209336, + -0.226830086097441, + 0.218861000711332, + -0.00713631958054, + 0.086731850555118, + ], + ]); assert_eq!(u, scipy_u); assert_eq!(l, scipy_l); @@ -71,53 +373,355 @@ pub fn test_dpotrf() { #[cfg(feature = "O3")] #[test] pub fn test_cholesky() { - let a = py_matrix( - vec![ - vec![2.9821852954666 , 2.680083137271475, 2.317961273223499, 2.134665492195094, 3.026598331879419, 2.283461999828011, 3.727890627691731, 1.948290349481972, 2.509358003244204, 2.354788397327008], - vec![2.680083137271475, 4.018906407367834, 2.341820593934314, 2.830765880629095, 3.552151618608445, 2.460655598487766, 3.630225204862198, 1.796569139726129, 2.846699793116624, 2.774282835105069], - vec![2.317961273223499, 2.341820593934314, 2.038910554565652, 1.966181894408401, 2.536384937997141, 1.886915794296248, 3.136970841370761, 1.905232489093743, 2.01726450417587 , 2.238391500871792], - vec![2.134665492195094, 2.830765880629095, 1.966181894408401, 3.301422256938514, 2.794302738298506, 2.205849011689008, 2.679880725689802, 1.555759606416891, 2.475715452431455, 2.116394736527704], - vec![3.026598331879419, 3.552151618608445, 2.536384937997141, 2.794302738298506, 4.337375745514422, 3.604658556851256, 4.143687579387594, 2.385563118734139, 3.321157832605267, 2.949560656237908], - vec![2.283461999828011, 2.460655598487766, 1.886915794296248, 2.205849011689008, 3.604658556851256, 3.413575093545912, 3.168622634363222, 1.907039710137486, 2.698561362072113, 2.249353149769269], - vec![3.727890627691731, 3.630225204862198, 3.136970841370761, 2.679880725689802, 4.143687579387594, 3.168622634363222, 5.142703863442649, 2.977549597798733, 3.210043437463944, 3.385961680761656], - vec![1.948290349481972, 1.796569139726129, 1.905232489093743, 1.555759606416891, 2.385563118734139, 1.907039710137486, 2.977549597798733, 2.230209604622137, 1.742552591231192, 2.252632855204552], - vec![2.509358003244204, 2.846699793116624, 2.01726450417587 , 2.475715452431455, 3.321157832605267, 2.698561362072113, 3.210043437463944, 1.742552591231192, 3.202516498869207, 2.228297655595811], - vec![2.354788397327008, 2.774282835105069, 2.238391500871792, 2.116394736527704, 2.949560656237908, 2.249353149769269, 3.385961680761656, 2.252632855204552, 2.228297655595811, 2.784914722669765] - ] - ); + let a = py_matrix(vec![ + vec![ + 2.9821852954666, + 2.680083137271475, + 2.317961273223499, + 2.134665492195094, + 3.026598331879419, + 2.283461999828011, + 3.727890627691731, + 1.948290349481972, + 2.509358003244204, + 2.354788397327008, + ], + vec![ + 2.680083137271475, + 4.018906407367834, + 2.341820593934314, + 2.830765880629095, + 3.552151618608445, + 2.460655598487766, + 3.630225204862198, + 1.796569139726129, + 2.846699793116624, + 2.774282835105069, + ], + vec![ + 2.317961273223499, + 2.341820593934314, + 2.038910554565652, + 1.966181894408401, + 2.536384937997141, + 1.886915794296248, + 3.136970841370761, + 1.905232489093743, + 2.01726450417587, + 2.238391500871792, + ], + vec![ + 2.134665492195094, + 2.830765880629095, + 1.966181894408401, + 3.301422256938514, + 2.794302738298506, + 2.205849011689008, + 2.679880725689802, + 1.555759606416891, + 2.475715452431455, + 2.116394736527704, + ], + vec![ + 3.026598331879419, + 3.552151618608445, + 2.536384937997141, + 2.794302738298506, + 4.337375745514422, + 3.604658556851256, + 4.143687579387594, + 2.385563118734139, + 3.321157832605267, + 2.949560656237908, + ], + vec![ + 2.283461999828011, + 2.460655598487766, + 1.886915794296248, + 2.205849011689008, + 3.604658556851256, + 3.413575093545912, + 3.168622634363222, + 1.907039710137486, + 2.698561362072113, + 2.249353149769269, + ], + vec![ + 3.727890627691731, + 3.630225204862198, + 3.136970841370761, + 2.679880725689802, + 4.143687579387594, + 3.168622634363222, + 5.142703863442649, + 2.977549597798733, + 3.210043437463944, + 3.385961680761656, + ], + vec![ + 1.948290349481972, + 1.796569139726129, + 1.905232489093743, + 1.555759606416891, + 2.385563118734139, + 1.907039710137486, + 2.977549597798733, + 2.230209604622137, + 1.742552591231192, + 2.252632855204552, + ], + vec![ + 2.509358003244204, + 2.846699793116624, + 2.01726450417587, + 2.475715452431455, + 3.321157832605267, + 2.698561362072113, + 3.210043437463944, + 1.742552591231192, + 3.202516498869207, + 2.228297655595811, + ], + vec![ + 2.354788397327008, + 2.774282835105069, + 2.238391500871792, + 2.116394736527704, + 2.949560656237908, + 2.249353149769269, + 3.385961680761656, + 2.252632855204552, + 2.228297655595811, + 2.784914722669765, + ], + ]); let u = a.cholesky(UPLO::Upper); let l = a.cholesky(UPLO::Lower); - let scipy_u = py_matrix( - vec![ - vec![ 1.726900488003463, 1.551961537963327, 1.34226684706273 , 1.236125362766596, 1.752618841041957, 1.322289278213136, 2.158717687318329, 1.128200705840593, 1.453099365410088, 1.363592409455782], - vec![ 0. , 1.268984551541246, 0.203843359082252, 0.718958209858647, 0.65576415850829 , 0.321921568031328, 0.220631829192057, 0.035969734306862, 0.466156555210059, 0.518556243550586], - vec![ 0. , 0. , 0.442355231459957, 0.362642811042644, 0.113550376983589, 0.104958994115387, 0.439514016124092, 0.867072131324973, -0.063762143110917, 0.683573627458849], - vec![ 0. , 0. , 0. , 1.060662825095517, 0.108612163548495, 0.284560665177766, -0.289042488015237, -0.168890722850109, 0.346460971292225, -0.179029330692296], - vec![ 0. , 0. , 0. , 0. , 0.90054762326669 , 1.14736846650923 , 0.218847271328198, 0.33819073440549 , 0.486759471799237, 0.17930981650712 ], - vec![ 0. , 0. , 0. , 0. , 0. , 0.391212348671252, 0.072001696614059, -0.069844847295321, -0.059587640909243, 0.135011494209335], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0.32274897849312 , 0.109238423598569, -0.011482954266517, -0.226830086097441], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.211084069199511, 0.154785258348754, 0.218861000711332], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.696448259994717, -0.007136319580541], - vec![ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.086731850555121] - ] - ); + let scipy_u = py_matrix(vec![ + vec![ + 1.726900488003463, + 1.551961537963327, + 1.34226684706273, + 1.236125362766596, + 1.752618841041957, + 1.322289278213136, + 2.158717687318329, + 1.128200705840593, + 1.453099365410088, + 1.363592409455782, + ], + vec![ + 0., + 1.268984551541246, + 0.203843359082252, + 0.718958209858647, + 0.65576415850829, + 0.321921568031328, + 0.220631829192057, + 0.035969734306862, + 0.466156555210059, + 0.518556243550586, + ], + vec![ + 0., + 0., + 0.442355231459957, + 0.362642811042644, + 0.113550376983589, + 0.104958994115387, + 0.439514016124092, + 0.867072131324973, + -0.063762143110917, + 0.683573627458849, + ], + vec![ + 0., + 0., + 0., + 1.060662825095517, + 0.108612163548495, + 0.284560665177766, + -0.289042488015237, + -0.168890722850109, + 0.346460971292225, + -0.179029330692296, + ], + vec![ + 0., + 0., + 0., + 0., + 0.90054762326669, + 1.14736846650923, + 0.218847271328198, + 0.33819073440549, + 0.486759471799237, + 0.17930981650712, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0.391212348671252, + 0.072001696614059, + -0.069844847295321, + -0.059587640909243, + 0.135011494209335, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0.32274897849312, + 0.109238423598569, + -0.011482954266517, + -0.226830086097441, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0.211084069199511, + 0.154785258348754, + 0.218861000711332, + ], + vec![ + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0.696448259994717, + -0.007136319580541, + ], + vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0.086731850555121], + ]); - let scipy_l = py_matrix( - vec![ - vec![ 1.726900488003463, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.551961537963327, 1.268984551541246, 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.34226684706273 , 0.203843359082252, 0.442355231459957, 0. , 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.236125362766596, 0.718958209858647, 0.362642811042644, 1.060662825095517, 0. , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.752618841041957, 0.65576415850829 , 0.113550376983589, 0.108612163548495, 0.90054762326669 , 0. , 0. , 0. , 0. , 0. ], - vec![ 1.322289278213136, 0.321921568031328, 0.104958994115387, 0.284560665177766, 1.14736846650923 , 0.391212348671252, 0. , 0. , 0. , 0. ], - vec![ 2.158717687318329, 0.220631829192057, 0.439514016124092, -0.289042488015237, 0.218847271328198, 0.072001696614058, 0.32274897849312 , 0. , 0. , 0. ], - vec![ 1.128200705840593, 0.035969734306862, 0.867072131324973, -0.168890722850108, 0.33819073440549 , -0.069844847295322, 0.109238423598569, 0.211084069199512, 0. , 0. ], - vec![ 1.453099365410088, 0.466156555210059, -0.063762143110917, 0.346460971292225, 0.486759471799238, -0.059587640909244, -0.011482954266516, 0.154785258348753, 0.696448259994717, 0. ], - vec![ 1.363592409455782, 0.518556243550586, 0.683573627458849, -0.179029330692296, 0.17930981650712 , 0.135011494209336, -0.226830086097441, 0.218861000711332, -0.00713631958054 , 0.086731850555118] - ] - ); + let scipy_l = py_matrix(vec![ + vec![1.726900488003463, 0., 0., 0., 0., 0., 0., 0., 0., 0.], + vec![ + 1.551961537963327, + 1.268984551541246, + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.34226684706273, + 0.203843359082252, + 0.442355231459957, + 0., + 0., + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.236125362766596, + 0.718958209858647, + 0.362642811042644, + 1.060662825095517, + 0., + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.752618841041957, + 0.65576415850829, + 0.113550376983589, + 0.108612163548495, + 0.90054762326669, + 0., + 0., + 0., + 0., + 0., + ], + vec![ + 1.322289278213136, + 0.321921568031328, + 0.104958994115387, + 0.284560665177766, + 1.14736846650923, + 0.391212348671252, + 0., + 0., + 0., + 0., + ], + vec![ + 2.158717687318329, + 0.220631829192057, + 0.439514016124092, + -0.289042488015237, + 0.218847271328198, + 0.072001696614058, + 0.32274897849312, + 0., + 0., + 0., + ], + vec![ + 1.128200705840593, + 0.035969734306862, + 0.867072131324973, + -0.168890722850108, + 0.33819073440549, + -0.069844847295322, + 0.109238423598569, + 0.211084069199512, + 0., + 0., + ], + vec![ + 1.453099365410088, + 0.466156555210059, + -0.063762143110917, + 0.346460971292225, + 0.486759471799238, + -0.059587640909244, + -0.011482954266516, + 0.154785258348753, + 0.696448259994717, + 0., + ], + vec![ + 1.363592409455782, + 0.518556243550586, + 0.683573627458849, + -0.179029330692296, + 0.17930981650712, + 0.135011494209336, + -0.226830086097441, + 0.218861000711332, + -0.00713631958054, + 0.086731850555118, + ], + ]); assert_eq!(u, scipy_u); assert_eq!(l, scipy_l); @@ -139,4 +743,4 @@ pub fn test_cholesky_panic_2() { // Not Positive Definite let a = ml_matrix("1 2;2 1"); a.cholesky(UPLO::Upper).print(); -} \ No newline at end of file +} diff --git a/tests/o3/mod.rs b/tests/o3/mod.rs index 8d7696fe..49a9389b 100644 --- a/tests/o3/mod.rs +++ b/tests/o3/mod.rs @@ -1,4 +1,3 @@ //! BLAS tests pub mod lapack; - diff --git a/tests/optimize.rs b/tests/optimize.rs index b7a257e4..951ecd89 100644 --- a/tests/optimize.rs +++ b/tests/optimize.rs @@ -43,10 +43,10 @@ fn test_GD() { } fn f(x: &Vec, p: Vec) -> Option> { - Some ( + Some( x.iter() .map(|t| AD1(*t, 0f64)) .map(|t| p[0] * t.powi(2) + p[1] * t + p[2]) - .collect() + .collect(), ) -} \ No newline at end of file +} diff --git a/tests/ordered_stat_test.rs b/tests/ordered_stat_test.rs index 54001e2b..051e1a44 100644 --- a/tests/ordered_stat_test.rs +++ b/tests/ordered_stat_test.rs @@ -74,6 +74,6 @@ fn quantile_length_test() { let data = vec![0f64; 7592]; let q1 = quantile(&data, QType::Type1); let q2 = quantile(&data, QType::Type2); - + assert!(q1.iter().zip(q2.iter()).all(|(x, y)| *x == *y)); } diff --git a/tests/polynomial.rs b/tests/polynomial.rs index 121183b9..f8a589a1 100644 --- a/tests/polynomial.rs +++ b/tests/polynomial.rs @@ -20,4 +20,4 @@ fn test_translate_x() { for i in -10..10 { assert_eq!(a.eval(i), b.eval(i - 6)); } -} \ No newline at end of file +} diff --git a/tests/root.rs b/tests/root.rs index b0d8128f..8584efdd 100644 --- a/tests/root.rs +++ b/tests/root.rs @@ -1,12 +1,21 @@ -use peroxide::fuga::*; use anyhow::Result; +use peroxide::fuga::*; #[test] fn test_cubic_root() -> Result<()> { let problem = Cubic; - let bisect = BisectionMethod { max_iter: 100, tol: 1e-6 }; - let newton = NewtonMethod { max_iter: 100, tol: 1e-6 }; - let false_pos = FalsePositionMethod { max_iter: 100, tol: 1e-6 }; + let bisect = BisectionMethod { + max_iter: 100, + tol: 1e-6, + }; + let newton = NewtonMethod { + max_iter: 100, + tol: 1e-6, + }; + let false_pos = FalsePositionMethod { + max_iter: 100, + tol: 1e-6, + }; let root_bisect = bisect.find(&problem)?; let root_newton = newton.find(&problem)?; let root_false_pos = false_pos.find(&problem)?; @@ -54,9 +63,18 @@ impl RootFindingProblem<1, 1, f64> for Cubic { #[test] fn test_sine_root() -> Result<()> { let problem = Sine; - let bisect = BisectionMethod { max_iter: 100, tol: 1e-6 }; - let newton = NewtonMethod { max_iter: 100, tol: 1e-6 }; - let false_pos = FalsePositionMethod { max_iter: 100, tol: 1e-6 }; + let bisect = BisectionMethod { + max_iter: 100, + tol: 1e-6, + }; + let newton = NewtonMethod { + max_iter: 100, + tol: 1e-6, + }; + let false_pos = FalsePositionMethod { + max_iter: 100, + tol: 1e-6, + }; let root_bisect = bisect.find(&problem)?; let root_newton = newton.find(&problem)?; let root_false_pos = false_pos.find(&problem)?; @@ -104,7 +122,10 @@ impl RootFindingProblem<1, 1, f64> for Sine { #[test] fn test_cosine_root() -> Result<()> { let problem = Cosine; - let newton = NewtonMethod { max_iter: 100, tol: 1e-6 }; + let newton = NewtonMethod { + max_iter: 100, + tol: 1e-6, + }; let root_newton = match newton.find(&problem) { Ok(x) => x, Err(e) => { diff --git a/tests/series.rs b/tests/series.rs index 9573c07e..1ac71403 100644 --- a/tests/series.rs +++ b/tests/series.rs @@ -5,14 +5,14 @@ use peroxide::fuga::*; #[test] fn series_map_test() { // Int - let a: Vec = vec![1,2,3,4,5]; + let a: Vec = vec![1, 2, 3, 4, 5]; let b: Vec = a.iter().map(|x| 2 * *x + 1).collect(); let s_a = Series::new(a); let s_b = Series::new(b); assert_eq!(s_b, s_a.map(|t: i32| 2 * t + 1)); // F64 - let a: Vec = vec![1.0,2.0,3.0,4.0,5.0]; + let a: Vec = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let b: Vec = a.iter().map(|x| 2f64 * *x + 1f64).collect(); let s_a = Series::new(a); let s_b = Series::new(b); @@ -22,13 +22,13 @@ fn series_map_test() { #[test] fn series_fold_test() { // Int - let a: Vec = vec![1,2,3,4,5]; + let a: Vec = vec![1, 2, 3, 4, 5]; let b = a.iter().fold(0, |x, y| x + *y); let s_a = Series::new(a); assert_eq!(b, s_a.fold(0i32, |x, y| x + y)); // F64 - let a: Vec = c!(1,2,3,4,5); + let a: Vec = c!(1, 2, 3, 4, 5); let b = a.iter().fold(0f64, |x, y| x + *y); let s_a = Series::new(a); assert_eq!(b, s_a.fold(0f64, |x, y| x + y)); @@ -37,15 +37,19 @@ fn series_fold_test() { #[test] fn series_filter_test() { // Int - let a: Vec = vec![1,2,3,4,5]; + let a: Vec = vec![1, 2, 3, 4, 5]; let b: Vec = a.clone().into_iter().filter(|x| *x % 2 == 0).collect(); let s_a = Series::new(a); let s_b = Series::new(b); assert_eq!(s_b, s_a.filter(|x: &i32| *x % 2 == 0)); // F64 - let a: Vec = c!(1,2,3,4,5); - let b: Vec = a.clone().into_iter().filter(|x| *x % 2f64 == 0f64).collect(); + let a: Vec = c!(1, 2, 3, 4, 5); + let b: Vec = a + .clone() + .into_iter() + .filter(|x| *x % 2f64 == 0f64) + .collect(); let s_a = Series::new(a); let s_b = Series::new(b); assert_eq!(s_b, s_a.filter(|x: &f64| *x % 2f64 == 0f64)); @@ -54,9 +58,9 @@ fn series_filter_test() { #[test] fn series_zip_with_test() { // Int - let a: Vec = vec![1,2,3,4,5]; - let b: Vec = vec![5,4,3,2,1]; - let c: Vec = a.iter().zip(b.iter()).map(|(x,y)| x + y).collect(); + let a: Vec = vec![1, 2, 3, 4, 5]; + let b: Vec = vec![5, 4, 3, 2, 1]; + let c: Vec = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect(); let s_a = Series::new(a); let s_b = Series::new(b); let s_c = Series::new(c); diff --git a/tests/special.rs b/tests/special.rs index 3954b4bf..acec5c6c 100644 --- a/tests/special.rs +++ b/tests/special.rs @@ -1,4 +1,4 @@ -use peroxide::fuga::{*, LambertWAccuracyMode::*}; +use peroxide::fuga::{LambertWAccuracyMode::*, *}; #[test] fn lambert_w_test() { diff --git a/tests/stat.rs b/tests/stat.rs new file mode 100644 index 00000000..73c37275 --- /dev/null +++ b/tests/stat.rs @@ -0,0 +1,30 @@ +extern crate peroxide; +use peroxide::fuga::*; + +#[test] +fn test_mean() { + let a: Vec = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + assert_eq!(a.mean(), 3.0); +} + +#[test] +fn test_mean_stable() { + let a: Vec = vec![1.0; 10000000]; + let diff = 10000.0; + let b = a.iter().map(|x| x + diff).collect::>(); + assert_eq!(a.mean(), b.mean() - diff); +} + +#[test] +fn test_variance() { + let a = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + assert_eq!(a.var(), 2.5); +} + +#[test] +fn test_variance_stable() { + let a = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let diff = 1000000000.0; + let b = a.iter().map(|x| x + diff).collect::>(); + assert_eq!(a.var(), b.var()); +} diff --git a/tests/weighted_uniform.rs b/tests/weighted_uniform.rs index b5f8c176..e7204995 100644 --- a/tests/weighted_uniform.rs +++ b/tests/weighted_uniform.rs @@ -31,6 +31,6 @@ fn f(x: f64) -> f64 { if x.abs() < 1f64 { 1f64 - x.abs() } else { - 0f64 + 0f64 } }