From 3874d1d9a0bbc4345424385d1bda508053f328a9 Mon Sep 17 00:00:00 2001 From: TrifanBogdan24 Date: Sat, 13 Jan 2024 16:25:14 +0200 Subject: [PATCH 1/2] added linear regression algorithm --- .../math/nushell-liniar-regression/README.md | 94 + .../math/nushell-liniar-regression/src/lib.rs | 1943 +++++++++++++++++ .../nushell-liniar-regression/src/matrix.rs | 39 + .../src/matrix_operations/create.rs | 77 + .../src/matrix_operations/determinant.rs | 60 + .../src/matrix_operations/empty.rs | 12 + .../src/matrix_operations/eye.rs | 25 + .../src/matrix_operations/inverse.rs | 63 + .../src/matrix_operations/mod.rs | 10 + .../src/matrix_operations/multiply.rs | 49 + .../src/matrix_operations/ones.rs | 16 + .../src/matrix_operations/shape.rs | 111 + .../src/matrix_operations/transpose.rs | 23 + .../src/matrix_operations/zeros.rs | 17 + .../nushell-liniar-regression/src/xy_data.rs | 173 ++ 15 files changed, 2712 insertions(+) create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/README.md create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/create.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/determinant.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/empty.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/eye.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/inverse.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/mod.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/multiply.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/ones.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/shape.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/transpose.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/zeros.rs create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs diff --git a/crates/nu-command/src/math/nushell-liniar-regression/README.md b/crates/nu-command/src/math/nushell-liniar-regression/README.md new file mode 100644 index 000000000000..f94a793f7666 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/README.md @@ -0,0 +1,94 @@ +# Matrix operation +The structures the store variables in lines and columns: +```rust +pub struct MatrixMN { + pub values: Vec>, +} +``` + +## Creating a matrix +```rust +let values: Vec = vec![1.0, 5.0]; +let mat: MatrixMN = MatrixMN::create_matrix(&values, 1, 2); // a vector of f64, nr of lines, nr of columns +``` + +## Accessing an element +```rust +mat.values[i][j] +``` +- element on the line `i - 1` and `j - 1` column +- indexing starts from `0` + + +# Linear Regression + +```rust +let nm1: String = String::from("X-var"); +let nm2: String = String::from("Y-var"); +let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; +let val2: Vec = vec![14.5, 140.1, 201.3, 220.5]; + +let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + +match dt.compute_linear_regression() { + Ok(line) => { + // d : y = a * x + b ; a = slope; b = intercept + println!("d : y = {} * x + {}", line.slope, line.intercept); + }, + Err(xbar) => { + // d : x = constant + println!("d : x = {}", xbar.x); + } +} + +``` + + +# Explaining The Algorithm +What is linear regression? +The best line that can go through a set of points P1(x1, y1), P2(x2, y2) ... P3(x3, y3) + +`d : y = a * x + b (x = ?, y = ?)` = the equation of the line + +We are to find the `slope (x)` and the `intercept (y)` of this line. + +Assuming that all points are on the same line, +we will obtain the following system of equations: +- a * x1 + b = y1 +- a * x2 + b = y2 +- a * x3 + b = y3 +- ..... +- a * xn + b = yn + + +Therefore, we can define the following matrices +``` + | 1 x1 | | y1 | + | 1 x2 | | y2 | +A = | .... | X = | b | B = | .. | + | 1 xn | | a | | yn | +``` + + +Now, the matrix `X` is the answear of the equations, +which are incompatible, therefore they are part of an indeterminate system `A * X = B`. + +## How to approximate the matrix X? +``` + A * X = B +At * | A * X = B +(At*A)^-1 | At * A * X = At * B +[(At * A)^(-1) * (At * A)] * X = (At * A)^(-1) * At * B +In * X = (At * A)^(-1) * At * B +X = (At * A)^(-1) * At * B +``` + + +Where: +- `In`: + 1. identity matrix with n lines and n columns + 2. has 1 on the diagonal and 0 in rest +- `At`: + 1. the transposed matrix + 2. columns of the initial matrix become rows of the transpose + 3. rows of the initial matrix become columns of the transpose diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs new file mode 100644 index 000000000000..5c3a7f93dc2e --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs @@ -0,0 +1,1943 @@ + +pub mod matrix_operations { + pub mod create; + pub mod determinant; + pub mod empty; + pub mod eye; + pub mod inverse; + pub mod multiply; + pub mod ones; + pub mod shape; + pub mod transpose; + pub mod zeros; +} + +pub mod matrix; +mod xy_data; + +pub fn add(left: usize, right: usize) -> usize { + left + right +} + + + +#[cfg(test)] +mod tests { + use std::arch::x86_64::{_mm256_stream_pd, _mm_aeskeygenassist_si128}; + use super::*; + use crate::matrix::MatrixMN; + use crate::xy_data::*; + use approx::abs_diff_eq; // since I compute integrals, + // I am interested in comparing only the first 5 decimals + + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } + + + #[test] + fn creating_a_matrix() { + let mut vector: Vec = Vec::new(); + + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let _mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + assert!(true); + } + + #[test] + fn check_matrix_dimensions() { + let mut vector: Vec = Vec::new(); + + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + if mat.nr_lines() != m || mat.height() != m { + assert!(false); + } + if mat.nr_columns() != n || mat.length() != n { + assert!(false); + } + assert!(true); + } + + + #[test] + fn check_matrix_elements() { + let mut vector: Vec = Vec::new(); + + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + let mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + let values: Vec = mat.get_vector(); + + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + + #[test] + /// the function will verify + /// the elements, the height and the length of the reshaped matrix + fn change_matrix_dimensions_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + let values: Vec = mat.get_vector(); + + mat.set_sizes(1, 20); + + if mat.nr_lines() != 1 || mat.height() != 1 { + assert!(false); + } + if mat.nr_columns() != 20 || mat.length() != 20 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// the function will verify + /// the elements, the height and the length of the reshaped matrix + fn change_matrix_dimensions_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values: Vec = mat.get_vector(); + + mat.set_sizes(2, 10); + + if mat.nr_lines() != 2 || mat.height() != 2 { + assert!(false); + } + if mat.nr_columns() != 10 || mat.length() != 10 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + #[test] + /// the function will verify + /// the elements, the height and the length of the reshaped matrix + fn change_matrix_dimensions_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + let values: Vec = mat.get_vector(); + + mat.set_sizes(4, 5); + + if mat.nr_lines() != 4 || mat.height() != 4 { + assert!(false); + } + if mat.nr_columns() != 5 || mat.length() != 5 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + #[test] + /// the function will verify + /// the elements, the height and the length of the reshaped matrix + fn change_matrix_dimensions_4() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + let values: Vec = mat.get_vector(); + + mat.set_sizes(20, 1); + + if mat.nr_lines() != 20 || mat.height() != 20 { + assert!(false); + } + if mat.nr_columns() != 1 || mat.length() != 1 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + #[test] + #[should_panic] + fn change_matrix_invalid_dimensions_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + // setting a smaller number of elements + mat.set_sizes(4, 4); // this should panic! + assert!(false, "The reshaped matrix must have the same number of elements as the initial matrix does."); + } + + #[test] + #[should_panic] + fn change_matrix_invalid_dimensions_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + // setting with a bigger number of elements + mat.set_sizes(4, 10); // this should panic! + assert!(false, "The reshaped matrix must have the same number of elements as the initial matrix does."); + } + + + #[test] + /// m = the height of the matrix = number of lines of the matrix + fn reshape_by_setting_height_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_height(1); // height = number of lines + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 1 || mat.height() != 1 { + assert!(false); + } + if mat.nr_columns() != 20 || mat.length() != 20 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// m = the height of the matrix = number of lines of the matrix + fn reshape_by_setting_height_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_height(2); // height = number of lines + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 2 || mat.height() != 2 { + assert!(false); + } + if mat.nr_columns() != 10 || mat.length() != 10 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// m = the height of the matrix = number of lines of the matrix + fn reshape_by_setting_height_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_height(4); // height = number of lines + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 4 || mat.height() != 4 { + assert!(false); + } + if mat.nr_columns() != 5 || mat.length() != 5 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// m = the height of the matrix = number of lines of the matrix + fn reshape_by_setting_height_4() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_height(5); // height = number of lines + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 5 || mat.height() != 5 { + assert!(false); + } + if mat.nr_columns() != 4 || mat.length() != 4 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + #[should_panic] + /// m = the height of the matrix = number of lines of the matrix + fn invalid_reshape_by_setting_height_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + mat.set_height(0); // should panic + assert!(false, "Cannot assign ZERO to be number of lines."); + } + + #[test] + #[should_panic] + /// m = the height of the matrix = number of lines of the matrix + fn invalid_reshape_by_setting_height_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + mat.set_height(3); // should panic + assert!(false, "The number of line must be divisor of the number of all elements."); + } + + #[test] + #[should_panic] + /// m = the height of the matrix = number of lines of the matrix + fn invalid_reshape_by_setting_height_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + mat.set_height(21); // should panic + assert!(false, "The number of line must be divisor of the number of all elements."); + } + + + #[test] + /// n = the length of the matrix = number of columns of the matrix + fn reshape_by_setting_length_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_length(1); // length = number of columns + + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 20 || mat.height() != 20 { + assert!(false); + } + if mat.nr_columns() != 1 || mat.length() != 1 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// n = the length of the matrix = number of columns of the matrix + fn reshape_by_setting_length_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_length(2); // length = number of columns + + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 10 || mat.height() != 10 { + assert!(false); + } + if mat.nr_columns() != 2 || mat.length() != 2 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// n = the length of the matrix = number of columns of the matrix + fn reshape_by_setting_length_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_length(4); // length = number of columns + + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 5 || mat.height() != 5 { + assert!(false); + } + if mat.nr_columns() != 4 || mat.length() != 4 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + /// n = the length of the matrix = number of columns of the matrix + fn reshape_by_setting_length_4() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector,m, n); + + mat.set_length(5); // length = number of columns + let values: Vec = mat.get_vector(); + + if mat.nr_lines() != 4 || mat.height() != 4 { + assert!(false); + } + if mat.nr_columns() != 5 || mat.length() != 5 { + assert!(false); + } + if values.len() != vector.len() { + assert!(false); + } + + for i in 0..=19 { + if values[i] != vector[i] { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + #[should_panic] + /// n = the length of the matrix = number of columns of the matrix + fn invalid_reshape_by_setting_length_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + mat.set_length(0); // should panic + assert!(false, "Cannot assign ZERO to be number of lines."); + } + + #[test] + #[should_panic] + /// n = the length of the matrix = number of columns of the matrix + fn invalid_reshape_by_setting_length_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + mat.set_length(3); // should panic + assert!(false, "The number of columns must be divisor of the number of all elements."); + } + + #[test] + #[should_panic] + /// n = the length of the matrix = number of columns of the matrix + fn invalid_reshape_by_setting_length_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mut mat: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + mat.set_length(21); // should panic + assert!(false, "The number of columns must be divisor of the number of all elements."); + } + + #[test] + fn resize_matrix_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values1: Vec = mat1.get_vector(); + + let mat2: MatrixMN = mat1.resize(1, 20); + let values2: Vec = mat2.get_vector(); + + if mat2.nr_lines() != 1 || mat2.height() != 1 { + assert!(false); + } + if mat2.nr_columns() != 20 || mat2.length() != 20 { + assert!(false); + } + + if values1.len() != values2.len() { + assert!(false); + } + + for i in 0..=19 { + if values1[i] != values2[i] { + assert!(false); + } + + assert!(true); + } + } + + #[test] + fn resize_matrix_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values1: Vec = mat1.get_vector(); + + let mat2: MatrixMN = mat1.resize(2, 10); + let values2: Vec = mat2.get_vector(); + + if mat2.nr_lines() != 2 || mat2.height() != 2 { + assert!(false); + } + if mat2.nr_columns() != 10 || mat2.length() != 10 { + assert!(false); + } + + if values1.len() != values2.len() { + assert!(false); + } + + for i in 0..=19 { + if values1[i] != values2[i] { + assert!(false); + } + + assert!(true); + } + } + + #[test] + fn resize_matrix_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values1: Vec = mat1.get_vector(); + + let mat2: MatrixMN = mat1.resize(4, 5); + let values2: Vec = mat2.get_vector(); + + if mat2.nr_lines() != 4 || mat2.height() != 4 { + assert!(false); + } + if mat2.nr_columns() != 5 || mat2.length() != 5 { + assert!(false); + } + + if values1.len() != values2.len() { + assert!(false); + } + + for i in 0..=19 { + if values1[i] != values2[i] { + assert!(false); + } + + assert!(true); + } + } + + #[test] + fn resize_matrix_4() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values1: Vec = mat1.get_vector(); + + let mat2: MatrixMN = mat1.resize(5, 4); + let values2: Vec = mat2.get_vector(); + + if mat2.nr_lines() != 5 || mat2.height() != 5 { + assert!(false); + } + if mat2.nr_columns() != 4 || mat2.length() != 4 { + assert!(false); + } + + if values1.len() != values2.len() { + assert!(false); + } + + for i in 0..=19 { + if values1[i] != values2[i] { + assert!(false); + } + + assert!(true); + } + } + + #[test] + fn resize_matrix_5() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values1: Vec = mat1.get_vector(); + + let mat2: MatrixMN = mat1.resize(10, 2); + let values2: Vec = mat2.get_vector(); + + if mat2.nr_lines() != 10 || mat2.height() != 10 { + assert!(false); + } + if mat2.nr_columns() != 2 || mat2.length() != 2 { + assert!(false); + } + + if values1.len() != values2.len() { + assert!(false); + } + + for i in 0..=19 { + if values1[i] != values2[i] { + assert!(false); + } + + assert!(true); + } + } + + #[test] + fn resize_matrix_6() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + let values1: Vec = mat1.get_vector(); + + let mat2: MatrixMN = mat1.resize(20, 1); + let values2: Vec = mat2.get_vector(); + + if mat2.nr_lines() != 20 || mat2.height() != 20 { + assert!(false); + } + if mat2.nr_columns() != 1 || mat2.length() != 1 { + assert!(false); + } + + if values1.len() != values2.len() { + assert!(false); + } + + for i in 0..=19 { + if values1[i] != values2[i] { + assert!(false); + } + + assert!(true); + } + } + + #[test] + #[should_panic] + fn invalid_resize_matrix_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + // setting a smaller number of elements + let _mat2: MatrixMN = mat1.resize(4, 4); // this should panic! + assert!(false, "The new resized matrix must have the same number of elements as the initial one."); + } + + #[test] + #[should_panic] + fn invalid_resize_matrix_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=20 { + vector.push(i as f64); + } + + let m: usize = 2; // number of lines + let n: usize = 10; // number of columns + + let mat1: MatrixMN = MatrixMN::create_matrix(&vector, m, n); + + // setting a bigger number of elements + let _mat2: MatrixMN = mat1.resize(5, 5); // this should panic! + assert!(false, "The new resized matrix must have the same number of elements as the initial one."); + } + + + #[test] + fn create_identical_square_matrices() { + for i in 1..=15 { + // identical square matrix with `i` lines and `i` columns + // eye(3) should look like this + // 1 0 0 + // 0 1 0 + // 0 0 1 + let identical: MatrixMN = MatrixMN::eye(i); + if identical.height() != i || identical.nr_lines() != i { + assert!(false); + } + if identical.length() != i || identical.nr_columns() != i { + assert!(false); + } + for j in 0..=(i - 1) { + for k in 0..=(i - 1) { + if j == k && identical.values[j][k] != 1.0 { + assert!(false); + } + if j != k && identical.values[j][k] != 0.0 { + assert!(false); + } + } + } + + assert!(true); + } + } + + + #[test] + fn create_matrices_with_zeros() { + // matrix with 10 lines and 2 columns + let mut mat: MatrixMN = MatrixMN::zeros(10, 2); + if mat.height() != 10 || mat.nr_lines() != 10 { + assert!(false); + } + if mat.length() != 2 || mat.nr_columns() != 2 { + assert!(false); + } + for i in 0..=9 { + for j in 0..=1 { + if mat.values[i][j] != 0.0 { + assert!(false); + } + } + } + + // matrix with 5 lines and 7 columns + mat = MatrixMN::zeros(5, 7); + if mat.height() != 5 || mat.nr_lines() != 5 { + assert!(false); + } + if mat.length() != 7 || mat.nr_columns() != 7 { + assert!(false); + } + for i in 0..=4 { + for j in 0..=6 { + if mat.values[i][j] != 0.0 { + assert!(false); + } + } + } + + assert!(true); + } + + #[test] + fn transpose_matrix_1() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0 , 4.0], 1, 4); + // 1.0 2.0 3.0 4.0 + + + let mat_t: MatrixMN = mat.transpose(); + mat_t.disp(); + // 1.0 + // 2.0 + // 3.0 + // 4.0 + + if mat_t.nr_lines() != 4 || mat_t.height() != 4 { + assert!(false); + } + if mat_t.nr_columns() != 1 || mat_t.length() != 1 { + assert!(false); + } + + for i in 0..=0 { + for j in 0..=3 { + if mat.values[i][j] != mat_t.values[j][i] { + assert!(false); + } + } + } + + assert!(true); + } + + #[test] + fn transpose_matrix_2() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0 , 4.0], 4, 1); + // 1.0 + // 2.0 + // 3.0 + // 4.0 + + let mat_t: MatrixMN = mat.transpose(); + // 1.0 2.0 3.0 4.0 + + + if mat_t.nr_lines() != 1 || mat_t.height() != 1 { + assert!(false); + } + if mat_t.nr_columns() != 4 || mat_t.length() != 4 { + assert!(false); + } + + for i in 0..=3 { + for j in 0..=0 { + if mat.values[i][j] != mat_t.values[j][i] { + assert!(false); + } + } + } + + assert!(true); + } + #[test] + fn transpose_matrix_3() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0 , 4.0, 5.0, 6.0, 7.0, 8.0], 2, 4); + // 1.0 2.0 3.0 4.0 + // 5.0 6.0 7.0 8.0 + + + let mat_t: MatrixMN = mat.transpose(); + // 1.0 5.0 + // 2.0 6.0 + // 3.0 7.0 + // 4.0 8.0 + + + if mat_t.nr_lines() != 4 || mat_t.height() != 4 { + assert!(false); + } + if mat_t.nr_columns() != 2 || mat_t.length() != 2 { + assert!(false); + } + + for i in 0..=1 { + for j in 0..=3 { + if mat.values[i][j] != mat_t.values[j][i] { + assert!(false); + } + } + } + + assert!(true); + } + + #[test] + fn transposed_transposed_matrix() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0 , 4.0, 5.0, 6.0, 7.0, 8.0], 2, 4); + // 1.0 2.0 3.0 4.0 + // 5.0 6.0 7.0 8.0 + + + let mat_t: MatrixMN = mat.transpose(); + // 1.0 5.0 + // 2.0 6.0 + // 3.0 7.0 + // 4.0 8.0 + + let mat_tt: MatrixMN = mat_t.transpose(); + // 1.0 2.0 3.0 4.0 + // 5.0 6.0 7.0 8.0 + + if mat_tt.nr_lines() != 2 || mat_tt.height() != 2 { + assert!(false); + } + if mat_tt.nr_columns() != 4 || mat_tt.length() != 4 { + assert!(false); + } + + for i in 0..=1 { + for j in 0..=3 { + if mat.values[i][j] != mat_tt.values[i][j] { + assert!(false); + } + } + } + + assert!(true); + } + + #[test] + fn multiply_vector_matrices_1() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0], 1, 3); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![3.0, 2.0, 1.0], 3, 1); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.length() != 1 || mat3.nr_lines() != 1 { + assert!(false); + } + if mat3.height() != 1 || mat3.nr_columns() != 1 { + assert!(false); + } + + if mat3.values[0][0] != 10.0 { + assert!(false); + } + + assert!(true); + } + + + #[test] + #[should_panic] + fn invalid_multiply_vector_matrices_1() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0], 1, 3); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![3.0, 2.0, 1.0], 3, 1); + let _mat3: MatrixMN = MatrixMN::mul(&mat2, &mat1); // should panic! + assert!(false, "The function should panic! \ + The operation can be applied only to matrices for which \ + the number of columns of the first one equals \ + the number of rows of the second one."); + } + + #[test] + fn multiply_vector_matrices_2() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![3.0, 2.0, 1.0], 3, 1); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0], 1, 3); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.length() != 3 || mat3.nr_lines() != 3 { + assert!(false); + } + if mat3.height() != 3 || mat3.nr_columns() != 3 { + assert!(false); + } + + if mat3.values[0][0] != 3.0 || mat3.values[0][1] != 6.0 || mat3.values[0][2] != 9.0 + || mat3.values[1][0] != 2.0 || mat3.values[1][1] != 4.0 || mat3.values[1][2] != 6.0 + || mat3.values[2][0] != 1.0 || mat3.values[2][1] != 2.0 || mat3.values[2][2] != 3.0 { + assert!(false) + } + } + + #[test] + #[should_panic] + fn invalid_multiply_vector_matrices_2() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0], 3, 1); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![3.0, 2.0, 1.0], 1, 3); + let _mat3: MatrixMN = MatrixMN::mul(&mat2, &mat1); + assert!(false, "The function should panic! \ + The operation can be applied only to matrices for which \ + the number of columns of the first one equals \ + the number of rows of the second one."); + } + + #[test] + fn multiply_square_matrices_1() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![4.0, 1.0, 3.0, 2.0], 2, 2); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.length() != 2 || mat3.nr_lines() != 2 { + assert!(false); + } + if mat3.height() != 2 || mat3.nr_columns() != 2 { + assert!(false); + } + + if mat3.values[0][0] != 10.0 || mat3.values[0][1] != 5.0 + || mat3.values[1][0] != 24.0 || mat3.values[1][1] != 11.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn multiply_square_matrices_2() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![4.0, 1.0, 3.0, 2.0], 2, 2); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.length() != 2 || mat3.nr_lines() != 2 { + assert!(false); + } + if mat3.height() != 2 || mat3.nr_columns() != 2 { + assert!(false); + } + + if mat3.values[0][0] != 7.0 || mat3.values[0][1] != 12.0 + || mat3.values[1][0] != 9.0 || mat3.values[1][1] != 14.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn multiply_non_square_matrices_1() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2, 3); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0], 3, 2); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.values[0][0] != 14.0 || mat3.values[0][1] != 32.0 + || mat3.values[1][0] != 32.0 || mat3.values[1][1] != 77.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn multiply_non_square_matrices_2() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 2, 4); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0], 4, 2); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.values[0][0] != 30.0 || mat3.values[0][1] != 70.0 + || mat3.values[1][0] != 70.0 || mat3.values[1][1] != 174.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn multiply_non_square_matrices_3() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0], 4, 2); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 2, 4); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.values[0][0] != 26.0 || mat3.values[0][1] != 32.0 + || mat3.values[0][2] != 38.0 || mat3.values[0][3] != 44.0 + || mat3.values[1][0] != 32.0 || mat3.values[1][1] != 40.0 + || mat3.values[1][2] != 48.0 || mat3.values[1][3] != 56.0 + || mat3.values[2][0] != 38.0 || mat3.values[2][1] != 48.0 + || mat3.values[2][2] != 58.0 || mat3.values[2][3] != 68.0 + || mat3.values[3][0] != 44.0 || mat3.values[3][1] != 56.0 + || mat3.values[3][2] != 68.0 || mat3.values[3][3] != 80.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn multiply_non_square_matrices_4() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 5.0], 1, 2); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 2, 4); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.values[0][0] != 26.0 || mat3.values[0][1] != 32.0 + || mat3.values[0][2] != 38.0 || mat3.values[0][3] != 44.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn multiply_non_square_matrices_5() { + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 5.0], 1, 2); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 5.0, 6.0, 7.0], 2, 3); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + + if mat3.values[0][0] != 26.0 || mat3.values[0][1] != 32.0 || mat3.values[0][2] != 38.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn determinant_eyes() { + for i in 1..=10 { + let mat: MatrixMN = MatrixMN::eye(i); + if mat.det() != 1.0 { + assert!(false); + } + } + assert!(true); + } + + #[test] + fn determinant_zeros() { + for i in 1..=10 { + let mat: MatrixMN = MatrixMN::zeros(i, i); + if mat.det() != 0.0 { + assert!(false); + } + } + assert!(true); + } + + #[test] + fn determinant_1() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0], 1, 1); + assert_eq!(mat.det(), 1.0); + } + + #[test] + fn determinant_2() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0], 2, 2); + assert_eq!(mat.det(), -2.0); + } + + #[test] + fn determinant_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=9 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 3, 3); + assert_eq!(mat.det(), 0.0); + } + + #[test] + fn determinant_4() { + let mut vector: Vec = Vec::new(); + for i in 1..=16 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 4, 4); + assert_eq!(mat.det(), 0.0); + } + + #[test] + fn determinant_5() { + let vals: Vec = vec![1.0, 1.0, 2.0, 4.0, 5.0, 7.0, 8.0, 9.0, 10.0]; + let mat: MatrixMN = MatrixMN::create_matrix(&vals, 3, 3); + assert_eq!(mat.det(), -5.0); + } + + #[test] + fn determinant_6() { + let mut vector: Vec = Vec::new(); + for i in 1..=25 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 5, 5); + assert_eq!(mat.det(), 0.0); + } + + #[test] + #[should_panic] + fn invalid_determinant_1() { + let mut vector: Vec = Vec::new(); + for i in 1..=16 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 2, 8); + let det: f64 = mat.det(); // should panic + assert!(false, "Should panic! The determinant can be applied only to square matrices."); + } + + #[test] + #[should_panic] + fn invalid_determinant_2() { + let mut vector: Vec = Vec::new(); + for i in 1..=16 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 16, 1); + let det: f64 = mat.det(); // should panic + assert!(false, "Should panic! The determinant can be applied only to square matrices."); + } + + + #[test] + fn inverse_matrix_1() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0], 1, 1); + if !mat.is_invertible() { + assert!(false); + } + let inv: MatrixMN = mat.inverse(); + + if inv.height() != 1 || inv.nr_lines() != 1 { + assert!(false); + } + if inv.length() != 1 || inv.nr_columns() != 1 { + assert!(false); + } + if inv.values[0][0] != 1.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn inverse_matrix_2() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![5.0], 1, 1); + if !mat.is_invertible() { + assert!(false); + } + let inv: MatrixMN = mat.inverse(); + + if inv.height() != 1 || inv.nr_lines() != 1 { + assert!(false); + } + if inv.length() != 1 || inv.nr_columns() != 1 { + assert!(false); + } + if inv.values[0][0] != 0.2 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn inverse_matrix_3() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![-0.5], 1, 1); + if !mat.is_invertible() { + assert!(false); + } + let inv: MatrixMN = mat.inverse(); + + if inv.height() != 1 || inv.nr_lines() != 1 { + assert!(false); + } + if inv.length() != 1 || inv.nr_columns() != 1 { + assert!(false); + } + inv.disp(); + if inv.values[0][0] != -2.0 { + assert!(false); + } + + assert!(true); + } + #[test] + fn inverse_matrix_4() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0], 2, 2); + if !mat.is_invertible() { + assert!(false); + } + let inv: MatrixMN = mat.inverse(); + + if inv.height() != 2 || inv.nr_lines() != 2 { + assert!(false); + } + if inv.length() != 2 || inv.nr_columns() != 2 { + assert!(false); + } + inv.disp(); + if inv.values[0][0] != -2.0 || inv.values[0][1] != 1.0 + || inv.values[1][0] != 1.5 || inv.values[1][1] != -0.5 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn inverse_matrix_5() { + let vals: Vec = vec![1.0, 1.0, 2.0, 4.0, 5.0, 7.0, 8.0, 9.0, 10.0]; + let mat: MatrixMN = MatrixMN::create_matrix(&vals, 3, 3); + if !mat.is_invertible() { + assert!(false); + } + let inv: MatrixMN = mat.inverse(); + + if inv.height() != 3 || inv.nr_lines() != 3 { + assert!(false); + } + if inv.length() != 3 || inv.nr_columns() != 3 { + assert!(false); + } + inv.disp(); + if inv.values[0][0] != 13.0/5.0 || inv.values[0][1] != -8.0/5.0 || inv.values[0][2] != 3.0/5.0 + || inv.values[1][0] != -16.0/5.0 || inv.values[1][1] != 6.0/5.0 || inv.values[1][2] != -1.0/5.0 + || inv.values[2][0] != 4.0/5.0 || inv.values[2][1] != 1.0/5.0 || inv.values[2][2] != -1.0/5.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn inverse_matrix_6() { + let vals: Vec = vec![1.0, 1.0, 2.0, 4.0, 5.0, 7.0, 8.0, 9.0, 10.0]; + let mat: MatrixMN = MatrixMN::create_matrix(&vals, 3, 3); + let matt: MatrixMN = mat.transpose(); + + if !matt.is_invertible() { + assert!(false); + } + + let inv: MatrixMN = matt.inverse(); + let invt: MatrixMN = inv.transpose(); + + if inv.height() != 3 || inv.nr_lines() != 3 { + assert!(false); + } + if inv.length() != 3 || inv.nr_columns() != 3 { + assert!(false); + } + + if invt.values[0][0] != 13.0/5.0 || invt.values[0][1] != -8.0/5.0 || invt.values[0][2] != 3.0/5.0 + || invt.values[1][0] != -16.0/5.0 || invt.values[1][1] != 6.0/5.0 || invt.values[1][2] != -1.0/5.0 + || invt.values[2][0] != 4.0/5.0 || invt.values[2][1] != 1.0/5.0 || invt.values[2][2] != -1.0/5.0 { + assert!(false); + } + + assert!(true); + } + + #[test] + fn inverse_invalid_1() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![0.0], 1, 1); + assert_eq!(mat.is_invertible(), false); + } + + #[test] + fn inverse_invalid_2() { + let mat: MatrixMN = MatrixMN::create_matrix(&vec![2.0, -1.0, -8.0, 4.0], 2, 2); + assert_eq!(mat.is_invertible(), false); + } + + #[test] + fn inverse_invalid_3() { + let mut vector: Vec = Vec::new(); + for i in 1..=9 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 3, 3); + assert_eq!(mat.is_invertible(), false); + } + + #[test] + fn inverse_invalid_4() { + let mut vector: Vec = Vec::new(); + for i in 1..=16 { + vector.push(i as f64); + } + + let mat: MatrixMN = MatrixMN::create_matrix(&vector, 4, 4); + assert_eq!(mat.is_invertible(), false); + } + + + #[test] + fn multiply_two_matrices_1() { + assert!(true); + } + + #[test] + fn test_all_matrix_functions() { + println!("\nCreating a matrix:"); + let vals: Vec = vec![1.0, 1.0, 2.0, 4.0, 5.0, 7.0, 8.0, 9.0, 10.0]; + let mut mat: MatrixMN = MatrixMN::create_matrix(&vals, 3, 3); + mat.disp(); // prints the matrix to stdout + + println!("\nCreating an identical 4x4 matrix:"); + mat = MatrixMN::eye(4); + mat.disp(); + + println!("\nCreating an identical matrix with 3 lines and 5 columns:"); + mat = MatrixMN::unsquare_eye(3, 5); + mat.disp(); + + println!("\nCreating an identical matrix with 5 lines and 4 columns:"); + mat = MatrixMN::unsquare_eye(5, 3); + mat.disp(); + + + println!("\nTransposing a matrix:"); + println!("Initial matrix:"); + mat = MatrixMN::create_matrix(&vals, 3, 3); + println!("Transposed matrix:"); + let matt: MatrixMN = mat.transpose(); + matt.disp(); + + println!("\nMultiply two matrices"); + let mat1: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 2, 4); + let mat2: MatrixMN = MatrixMN::create_matrix(&vec![1.0, 5.0, 2.0, 6.0, 3.0, 7.0, 4.0, 8.0], 4, 2); + let mat3: MatrixMN = MatrixMN::mul(&mat1, &mat2); + mat3.disp(); + + println!("\nThe inverse of a matrix"); + let inv: MatrixMN = mat.inverse(); + inv.disp(); + + assert!(true); + } + + #[test] + fn create_data_set_1() { + let dt: DataSet = DataSet { + x_name: String::from("X"), + y_name: String::from("Y"), + x_values: vec![1.0, 2.0, 3.0, 4.0], + y_values: vec![11.0, 15.0, 26.0, 55.0], + }; + + dt.validate_data_set(); + assert!(true); + } + + #[test] + fn create_data_set_2() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![11.0, 15.0, 26.0, 55.0]; + + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + if dt.x_name != "X-var" || dt.y_name != "Y-var" { + assert!(false); + } + + if dt.x_values.len() != dt.y_values.len() + || dt.x_values.len() != val1.len() || dt.y_values.len() != val2.len() { + assert!(false); + } + + for i in 0..dt.x_values.len() { + if dt.x_values[i] != val1[i] || dt.y_values[i] != val2[i] { + assert!(false); + } + } + + assert!(true); + } + + #[test] + #[should_panic] + fn create_invalid_data_set_1() { + let dt: DataSet = DataSet { + x_name: String::from(""), + y_name: String::from("Y"), + x_values: vec![1.0, 2.0, 3.0, 4.0], + y_values: vec![11.0, 15.0, 26.0, 55.0], + }; + + dt.validate_data_set(); + assert!(true); + } + + #[test] + #[should_panic] + fn create_invalid_data_set_2() { + let dt: DataSet = DataSet { + x_name: String::from("X"), + y_name: String::new(), + x_values: vec![1.0, 2.0, 3.0, 4.0], + y_values: vec![11.0, 15.0, 26.0, 55.0], + }; + + dt.validate_data_set(); + assert!(true); + } + + #[test] + #[should_panic] + fn create_invalid_data_set_3() { + let dt: DataSet = DataSet { + x_name: String::from("X"), + y_name: String::from("Y"), + x_values: vec![], + y_values: vec![11.0, 15.0, 26.0, 55.0], + }; + + dt.validate_data_set(); + assert!(true); + } + + #[test] + #[should_panic] + fn create_invalid_data_set_4() { + let dt: DataSet = DataSet { + x_name: String::from("X"), + y_name: String::from("Y"), + x_values: vec![1.0, 2.0, 3.0, 4.0], + y_values: Vec::new(), + }; + + dt.validate_data_set(); + assert!(true); + } + + #[test] + #[should_panic] + fn create_invalid_data_set_5() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0, 11.5]; // len = 5 + let val2: Vec = vec![11.0, 15.0, 26.0, 55.0]; // len = 4 + + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + assert!(true); + } + + + #[test] + fn create_data_set_from_vector_1() { + let val2: Vec = vec![1.0, 2.0]; + + let dt: DataSet = DataSet::new_from_vec(val2.clone()); + + if dt.x_name != "X" || dt.y_name != "Y" { + assert!(false); + } + + // check the X-axis + if dt.x_values[0] != 0.0 || dt.x_values[1] != 1.0 { + assert!(false); + } + + // check the Y-axis + if dt.y_values[0] != 1.0 || dt.y_values[1] != 2.0 { + assert!(false); + } + + assert!(true); + } + + + #[test] + #[should_panic] + fn create_invalid_data_set_from_vector_1() { + let val2: Vec = vec![]; + let dt: DataSet = DataSet::new_from_vec(val2.clone()); + assert!(true); + } + + #[test] + fn linear_regression_1() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![10.0, 20.0, 30.0, 40.0]; + + // EQUATION d : y = 10 * x + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // the line will be the first bisector + match dt.compute_linear_regression() { + Ok(line) => { + println!("{}", dt.equation_linear_regression()); + // Compare with a precision of the first 10 decimals + + let precision = 1e-10; + + if abs_diff_eq!(line.slope, 10.0, epsilon = precision) == false { + assert!(false); + } + + if abs_diff_eq!(line.intercept, 0.0, epsilon = precision) == false { + assert!(false); + } + + }, + Err(_) => assert!(false), + } + assert!(true); + } + + + #[test] + fn linear_regression_2() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![11.0, 11.0, 11.0, 11.0]; + + // EQUATION d : y = 11 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // the line will be a line parallel to OY + match dt.compute_linear_regression() { + Ok(line) => { + // d : y = a * x + b ; a = slope; b = intercept + let precision = 1e-10; + + if abs_diff_eq!(line.slope, 0.0, epsilon = precision) == false { + assert!(false); + } + + if abs_diff_eq!(line.intercept, 11.0, epsilon = precision) == false { + assert!(false); + } + + assert!(true); + }, + Err(_) => { + assert!(false); + }, + } + assert!(true); + } + + + #[test] + fn linear_regression_3() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![7.0, 7.0, 7.0, 7.0]; + let val2: Vec = vec![1.0, 1.5, 2.5, 4.0]; + + // EQUATION d : x = 7 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + match dt.compute_linear_regression() { + Ok(_) => { + assert!(false); + }, + Err(xbar) => { + let precision = 1e-10; + + if abs_diff_eq!(xbar.x, 7.0, epsilon = precision) == false { + assert!(false); + } + assert!(true); + } + } + + assert!(true); + } + + #[test] + fn linear_regression_4() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![14.5, 140.1, 201.3, 220.5]; + + // EQUATION d : y = 67.92 * x - 25.70 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + match dt.compute_linear_regression() { + Ok(line) => { + // d : y = a * x + b ; a = slope; b = intercept + let precision = 1e-2; + + if abs_diff_eq!(line.slope, 67.92, epsilon = precision) == false { + assert!(false); + } + + if abs_diff_eq!(line.intercept, -25.70, epsilon = precision) == false { + assert!(false); + } + + assert!(true); + }, + Err(_) => { + assert!(false); + } + } + + assert!(true); + } + + + #[test] + fn linear_regression_5() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![220.5, 201.3, 140.1, 14.5]; + + // EQUATION d : y = -67.919 * x + 313.9 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + match dt.compute_linear_regression() { + Ok(line) => { + // d : y = a * x + b ; a = slope; b = intercept + let precision = 1e-2; + + if abs_diff_eq!(line.slope, -67.919, epsilon = precision) == false { + assert!(false); + } + + if abs_diff_eq!(line.intercept, 313.900, epsilon = precision) == false { + assert!(false); + } + + assert!(true); + }, + Err(_) => { + assert!(false); + } + } + + assert!(true); + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix.rs new file mode 100644 index 000000000000..b0889123f9bc --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix.rs @@ -0,0 +1,39 @@ + + +pub struct MatrixMN { + pub values: Vec>, +} + +impl MatrixMN { + /// the function will display the matrix + pub fn disp(&self) { + + if self.values.is_empty() { + println!("[ ]"); + return; + } + + let m: usize = self.values.len(); // number of lines + let n: usize = self.values[0].len(); // number of columns + + print!("["); + + for i in 0..=(m - 1) { + for j in 0..=(n - 1) { + print!("{}", self.values[i][j]); + + if j != n - 1 { + print!(", "); + } + } + + if i != m - 1 { + println!(";"); + } else { + println!("]"); + } + } + + return; + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/create.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/create.rs new file mode 100644 index 000000000000..aeaa7dbc5ba9 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/create.rs @@ -0,0 +1,77 @@ +use crate::matrix::MatrixMN; + + + +/// m = the number of lines (rows) = the height of the matrix +/// n = the number of columns = the lengths of the matrix +impl MatrixMN { + pub fn validate_new_matrix_creation(m: usize, n: usize) { + if m == 0 && n == 0 { + eprintln!("Err: the new matrix must contain at least one line and one column"); + panic!("Cannot create a matrix with 0 lines nad 0 columns!"); + } else if m == 0 { + eprintln!("Err: the new matrix must contain at least one line."); + panic!("Cannot create a matrix with 0 lines!"); + } else if n == 0 { + eprintln!("Err: the new matrix must contain at least one column."); + panic!("Cannot create a matrix with 0 columns!"); + } + } +} + +impl MatrixMN { + /// the function is given a vector that will be transformed into a matrix + /// as the vector's elements are iterated, + /// we fill the matrix from left to right, from the upper line to the bottom + /// vector : the values of the matrix + /// m : the number of lines + /// n : the number of columns + pub fn create_matrix(vector: &Vec, m: usize, n: usize) -> Self { + Self::validate_new_matrix_creation(m, n); + + if vector.is_empty() { + panic!("Cannot a matrix with no elements!"); + } + + if vector.len() != m * n { + panic!("Invalid size! The matrix expects exactly {} elements", m * n); + } + + let mut vals = Vec::with_capacity(m); + + for i in 0..=(m - 1) { + let mut line = Vec::with_capacity(n); + + for j in 0..=(n - 1) { + let idx: usize = (i * n) + j; + line.push(vector[idx]); + } + + vals.push(line); + } + + return MatrixMN { + values: vals, + } + } +} + + + +impl MatrixMN { + /// the function will transform a matrix into a vector + /// using the following algorithm: + /// the matrix will be traversed from the last line to the last (from upper to bottom) + /// and each line, from the first element (farthest left) to the last element (farthest right) + pub fn get_vector(&self) -> Vec { + let mut vector: Vec = Vec::new(); + + for line in &self.values { + for el in line { + vector.push(*el); + } + } + + return vector; + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/determinant.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/determinant.rs new file mode 100644 index 000000000000..5db317a9ace1 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/determinant.rs @@ -0,0 +1,60 @@ +use crate::matrix::MatrixMN; + +impl MatrixMN { + pub fn delete_line_column(&self, lin: usize, col: usize) -> Self { + + let mut new_vector: Vec = Vec::new(); + + let m: usize = self.nr_lines(); + let n: usize = self.nr_columns(); + + for i in 0..=(m - 1) { + for j in 0..=(n - 1) { + if i != lin && j != col { + new_vector.push(self.values[i][j]); + } + } + } + + return Self::create_matrix(&new_vector, m - 1, n - 1); + } +} + + +impl MatrixMN { + /// the function calculates the value of a matrix's determinant + /// + /// only square matrices have such operation + pub fn det(&self) -> f64 { + let mat = MatrixMN::create_matrix(&(self.get_vector()), self.nr_lines(), self.nr_columns()); + return Self::det_helper(mat); + } + + pub fn det_helper(mat: Self) -> f64 { + let m: usize = mat.nr_lines(); + let n: usize = mat.nr_columns(); + + if m * n == 0 { + panic!("Empty matrix."); + } else if m != n { + panic!("The determinant can be applied only to square matrices."); + } + + if m == 1 { + return mat.values[0][0]; + } + + let mut val: f64 = 0.0f64; + + for i in 0..=(m - 1) { + let cut_mat: Self = mat.delete_line_column(0, i); + + match i % 2 == 0 { + true => val += mat.values[0][i] * Self::det_helper(cut_mat), + false => val -= mat.values[0][i] * Self::det_helper(cut_mat), + } + } + + return val; + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/empty.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/empty.rs new file mode 100644 index 000000000000..baa73732f0ab --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/empty.rs @@ -0,0 +1,12 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + /// creates a matrix with no elements + pub fn empty() -> Self { + let vals: Vec> = Vec::new(); + return MatrixMN { + values: vals, + } + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/eye.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/eye.rs new file mode 100644 index 000000000000..571ad307489f --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/eye.rs @@ -0,0 +1,25 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + pub fn unsquare_eye(m: usize, n: usize) -> Self { + Self::validate_new_matrix_creation(m, n); + + let mut mat: Self = Self::zeros(m, n); + + let idx: usize = if m > n { n } else { m }; + + for i in 0..=(idx - 1) { + mat.values[i][i] = 1.0f64; + } + + return mat; + } +} + +impl MatrixMN { + pub fn eye(m: usize) -> Self { + return Self::unsquare_eye(m, m); + } +} + diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/inverse.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/inverse.rs new file mode 100644 index 000000000000..6321b9cd11d1 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/inverse.rs @@ -0,0 +1,63 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + pub fn is_invertible(&self) -> bool { + let m: usize = self.nr_lines(); + let n: usize = self.nr_columns(); + + if m * n == 0 || m != n { + return false; + } + + if self.det() == 0.0 { + return false; + } + + if self.det().abs() < f64::EPSILON { + return false; + } + + return true; + } +} + +impl MatrixMN { + pub fn inverse(&self) -> Self { + let m: usize = self.nr_lines(); + let n: usize = self.nr_columns(); + let det: f64 = self.det(); + + if det.abs() < f64::EPSILON { + panic!("The matrix is singular, and its inverse does not exist."); + } + + let mut inverse_matrix: MatrixMN = MatrixMN::empty(); + + + if n == 1 { + // the `determinant` of a square matrix with 1 line and 1 columns + // equals the singe element of the matrix + return MatrixMN::create_matrix(&vec![1.0 / self.values[0][0]], 1, 1); + } + + for i in 0..=(m - 1) { + let mut row = Vec::new(); + + for j in 0..=(n - 1) { + let cofactor = match (i + j) % 2 == 0 { + true => self.delete_line_column(i, j).det(), + false => -self.delete_line_column(i, j).det(), + }; + + row.push(cofactor / det); + } + + inverse_matrix.values.push(row); + } + + + // Transpose the result because adjucate is transposed + return inverse_matrix.transpose(); + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/mod.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/mod.rs new file mode 100644 index 000000000000..42ba653cccbd --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/mod.rs @@ -0,0 +1,10 @@ +pub mod create; +pub mod determinant; +pub mod empty; +pub mod eye; +pub mod inverse; +pub mod multiply; +pub mod ones; +pub mod shape; +pub mod transpose; +pub mod zeros; diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/multiply.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/multiply.rs new file mode 100644 index 000000000000..c3862a0d4503 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/multiply.rs @@ -0,0 +1,49 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + /// the function multiplies two matrices + /// + /// for a valid operation + /// the number of columns of the first matrix + /// must equal + /// the number of rows of the second matrix + pub fn mul(mat1: &Self, mat2: &Self) -> Self { + let m1: usize = mat1.nr_lines(); + let n1: usize = mat1.nr_columns(); + + let m2: usize = mat2.nr_lines(); + let n2: usize = mat2.nr_columns(); + + match (m1 * n1 == 0, m2 * n2 == 0) { + (true, true) => panic!("Both matrices are empty"), + (true, false) => panic!("The first matrix is empty"), + (false, true) => panic!("The first matrix is empty"), + _ => (), + + } + + if n1 != m2 { + panic!("The number of COLUMNS of the FIRST matrix must equal \ + the number of LINES of the SECOND matrix"); + } + + let m: usize = m1; // nr lines first matrix == nr lines final matrix + let p: usize = m2; // nr cols first matrix = nr lines second matrix + let n: usize = n2; // nr cols second matrix == nr cols final matrix + let mut ret_mat: MatrixMN = Self::zeros(m, n); + + for i in 0..=(m - 1) { + for j in 0..=(n - 1) { + + ret_mat.values[i][j] = 0.0f64; + for k in 0..=(p - 1) { + ret_mat.values[i][j] += mat1.values[i][k] * mat2.values[k][j]; + } + + } + } + + return ret_mat; + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/ones.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/ones.rs new file mode 100644 index 000000000000..1a3994ea2c46 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/ones.rs @@ -0,0 +1,16 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + pub fn ones(m: usize, n: usize) -> Self { + Self::validate_new_matrix_creation(m, n); + + let mut vector: Vec = Vec::new(); + + for _i in 0..=(m * n - 1) { + vector.push(1.0f64); + } + + return Self::create_matrix(&vector, m, n); + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/shape.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/shape.rs new file mode 100644 index 000000000000..d05e45fc93cd --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/shape.rs @@ -0,0 +1,111 @@ +use crate::matrix::MatrixMN; + +impl MatrixMN { + /// the number of columns will be returned + pub fn length(&self) -> usize { + return self.nr_columns(); + } + + /// the number of lines will be returned + pub fn height(&self) -> usize { + return self.nr_lines(); + } + + + /// the number of columns will be returned + pub fn nr_columns(&self) ->usize { + if self.values.is_empty() { + return 0; + } + return self.values[0].len(); + } + + /// the number of the lines will be returned + pub fn nr_lines(&self) -> usize { + if self.values.is_empty() { + return 0; + } + return self.values.len(); + } +} + + +impl MatrixMN { + /// the function is given a matrix + pub fn resize(&self, m: usize, n: usize) -> Self { + let vector: Vec = self.get_vector(); + + Self::validate_new_matrix_creation(m, n); + + if vector.is_empty() { + panic!("Cannot a matrix with no elements!"); + } + + if vector.len() != m * n { + panic!("Invalid size! The matrix expects exactly {} elements", m * n); + } + + return Self::create_matrix(&vector, m, n); + } +} + + +impl MatrixMN { + /// + pub fn set_sizes(&mut self, m:usize, n: usize) { + let vector: Vec = self.get_vector(); + + if vector.is_empty() { + panic!("Cannot create an empty matrix!"); + } + + if vector.len() != m * n { + panic!("Invalid size! The matrix expects exactly {} elements", m * n); + } + + + let new_mat: Self = Self::create_matrix(&vector, m, n); + self.values = new_mat.values; + } +} + +impl MatrixMN { + /// length = number of column + pub fn set_length(&mut self, n: usize) { + if n == 0 { + panic!("Cannot set the number of column with 0."); + } + + let vector: Vec = self.get_vector(); + + if vector.len() % n != 0 { + eprintln!("Err: cannot split the matrix in {} columns", n); + eprintln!("Err: The length (number of columns) must be a divisor of {}", vector.len()); + panic!("Invalid resize"); + } + + let new_mat: Self = Self::create_matrix(&vector, vector.len() / n, n); + self.values = new_mat.values; + } +} + +impl MatrixMN { + /// height = number of rows (lines) + pub fn set_height(&mut self, m: usize) { + + if m == 0 { + panic!("Cannot set the number of lines with 0."); + } + + let vector: Vec = self.get_vector(); + + if vector.len() % m != 0 { + eprintln!("Err: cannot split the matrix in {} lines.", m); + eprintln!("Err: The height (number of lines) must be a divisor of {}", vector.len()); + panic!("Invalid resize"); + } + + let new_mat: Self = Self::create_matrix(&vector, m, vector.len() / m); + self.values = new_mat.values; + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/transpose.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/transpose.rs new file mode 100644 index 000000000000..87cbdc9ca8b0 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/transpose.rs @@ -0,0 +1,23 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + /// the function will create the transposed matrix of the input + /// in the transposed matrix: + /// the initial columns become rows of the transposed matrix + /// the initial rows become columns of the transposed matrix + pub fn transpose(&self) -> Self { + let m: usize = self.nr_lines(); + let n: usize = self.nr_columns(); + + let mut vector: Vec = Vec::new(); + + for j in 0..=(n - 1) { + for i in 0..=(m - 1) { + vector.push(self.values[i][j].clone()); + } + } + + return Self::create_matrix(&vector, n, m); + } +} diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/zeros.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/zeros.rs new file mode 100644 index 000000000000..7f08d4c2f6c3 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/matrix_operations/zeros.rs @@ -0,0 +1,17 @@ +use crate::matrix::MatrixMN; + + +impl MatrixMN { + pub fn zeros(m: usize, n: usize) -> Self { + Self::validate_new_matrix_creation(m, n); + + let mut vector: Vec = Vec::new(); + + for _i in 0..=(m * n - 1) { + vector.push(0.0f64); + } + + return Self::create_matrix(&vector, m, n); + } +} + diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs new file mode 100644 index 000000000000..8a844a356cb0 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs @@ -0,0 +1,173 @@ +use crate::matrix::MatrixMN; + +pub struct DataSet { + pub x_name: String, + pub y_name: String, + pub x_values: Vec, + pub y_values: Vec, +} + + +/// d : x = a +pub struct XBar { + pub x: f64, +} + + +/// d : y = a * x + b +/// a = slope +/// b = intercept +pub struct Line { + pub slope: f64, + pub intercept: f64, +} + + +impl DataSet { + pub fn validate_data_set(&self) { + if self.x_name.is_empty() { + panic!("Missing name of the input variable."); + } + if self.y_name.is_empty() { + panic!("Missing name of the output variable."); + } + if self.x_values.is_empty() { + panic!("The are no values for the X-axis."); + } + if self.y_values.is_empty() { + panic!("The are no values for the Y-axis."); + } + if self.x_values.len() != self.y_values.len() { + panic!("Different number of elements for the input and output values."); + } + } +} + +impl DataSet { + pub fn new(name1: String, name2: String, val1: Vec, val2: Vec) -> Self { + let ret: DataSet = DataSet { + x_name: name1, + y_name: name2, + x_values: val1, + y_values: val2, + }; + + ret.validate_data_set(); + return ret; + } +} + + +impl DataSet { + /// indexing starts from 0 + pub fn new_from_vec(val2: Vec) -> Self { + let mut val1: Vec = Vec::new(); + + for i in 0..=(val2.len() - 1) { + val1.push(i as f64); + } + + let ret: DataSet = DataSet { + x_name: String::from("X"), + y_name: String::from("Y"), + x_values: val1, + y_values: val2, + }; + + ret.validate_data_set(); + return ret; + } +} + + +pub fn avg(vector: &Vec) -> f64 { + if vector.is_empty() { + return 0.0f64; + } + + let mut sum: f64 = 0.0f64; + for el in vector { + sum += el; + } + + return sum / (vector.len() as f64); +} + + +impl DataSet { + pub fn print_horizontally(&self) { + self.validate_data_set(); + println!("{}: {:?}", self.x_name, self.x_values); + println!("{}: {:?}", self.y_name, self.y_values); + } + + pub fn print_vertically(&self) { + self.validate_data_set(); + println!("X = {}, Y = {}", self.x_name, self.y_name); + for i in 0..self.x_values.len() { + println!("{}, {}", self.x_values[i], self.y_values[i]); + } + } +} + +impl DataSet { + /// Result + /// Err -> the equation of a vertical line + /// Ok -> the equation of a (oblique / horizontal) line + pub fn compute_linear_regression(&self) -> Result { + self.validate_data_set(); + + let len: usize = self.x_values.len(); + let mut a: MatrixMN = MatrixMN::ones(len, 2); + let mut b: MatrixMN = MatrixMN::ones(len, 1); + + for i in 0..=(len - 1) { + a.values[i][1] = self.x_values[i]; + b.values[i][0] = self.y_values[i]; + } + + let stage1: MatrixMN = MatrixMN::mul(&a.transpose(), &a); + + + if !stage1.is_invertible() { + let ret = XBar { + x: avg(&self.x_values), + }; + return Err(ret); + } + + let stage2: MatrixMN = stage1.inverse(); + let stage3: MatrixMN = MatrixMN::mul(&stage2, &a.transpose()); + let stage4: MatrixMN = MatrixMN::mul(&stage3, &b); + + let ret = Line { + slope: stage4.values[1][0], + intercept: stage4.values[0][0], + }; + + return Ok(ret); + } +} + + +impl DataSet { + pub fn equation_linear_regression(&self) -> String { + match self.compute_linear_regression() { + Ok(line) => { + return format!("d : y = a * {:.10} + {:.10}", line.slope, line.intercept); + }, + Err(bar) => { + return format!("d : x = {:.10}", bar.x); + } + } + } +} + + +impl DataSet { + pub fn disp(&self) { + println!("\"{}\": {:?}", self.x_name, self.x_values); + println!("\"{}\": {:?}", self.y_name, self.y_values); + } + +} \ No newline at end of file From 0c86f5928bf3c62049548e968c537f9a11c318a2 Mon Sep 17 00:00:00 2001 From: TrifanBogdan24 Date: Sat, 13 Jan 2024 18:52:07 +0200 Subject: [PATCH 2/2] text equation --- .../math/nushell-liniar-regression/Cargo.toml | 8 +++ .../math/nushell-liniar-regression/src/lib.rs | 72 +++++++++++++++++++ .../nushell-liniar-regression/src/xy_data.rs | 40 +++++++++-- 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 crates/nu-command/src/math/nushell-liniar-regression/Cargo.toml diff --git a/crates/nu-command/src/math/nushell-liniar-regression/Cargo.toml b/crates/nu-command/src/math/nushell-liniar-regression/Cargo.toml new file mode 100644 index 000000000000..347db221ee48 --- /dev/null +++ b/crates/nu-command/src/math/nushell-liniar-regression/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "nushell-liniar-regression" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs index 5c3a7f93dc2e..be56f4ba167e 100644 --- a/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/lib.rs @@ -1940,4 +1940,76 @@ mod tests { assert!(true); } + + + #[test] + fn verify_equation_1() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![10.0, 20.0, 30.0, 40.0]; + + // EQUATION d : y = 10 * x + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // println!("{:?}", dt.equation_linear_regression()); + assert_eq!(dt.equation_linear_regression(), "d : y = 10.0000000000 * x"); + } + + + #[test] + fn verify_equation_2() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![11.0, 11.0, 11.0, 11.0]; + + // EQUATION d : y = 11 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // println!("{:?}", dt.equation_linear_regression()); + assert_eq!(dt.equation_linear_regression(), "d : y = 11.0000000000"); + } + + #[test] + fn verify_equation_3() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![7.0, 7.0, 7.0, 7.0]; + let val2: Vec = vec![1.0, 1.5, 2.5, 4.0]; + + // EQUATION d : x = 7 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // println!("{:?}", dt.equation_linear_regression()); + assert_eq!(dt.equation_linear_regression(), "d : x = 7.0000000000"); + } + + #[test] + fn verify_equation_4() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![14.5, 140.1, 201.3, 220.5]; + + // EQUATION d : y = 67.92 * x - 25.70 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // println!("{:?}", dt.equation_linear_regression()); + assert_eq!(dt.equation_linear_regression(), "d : y = 67.9200000000 * x - 25.7000000000"); + } + + #[test] + fn verify_equation_5() { + let nm1: String = String::from("X-var"); + let nm2: String = String::from("Y-var"); + let val1: Vec = vec![1.0, 2.0, 3.0, 4.0]; + let val2: Vec = vec![220.5, 201.3, 140.1, 14.5]; + + // EQUATION d : y = -67.919 * x + 313.9 + let dt: DataSet = DataSet::new(nm1.clone(), nm2.clone(), val1.clone(), val2.clone()); + + // to display : println!("{:?}", dt.equation_linear_regression()); + assert_eq!(dt.equation_linear_regression(), "d : y = -67.9200000000 * x + 313.9000000000"); + } } diff --git a/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs b/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs index 8a844a356cb0..9256aa09733b 100644 --- a/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs +++ b/crates/nu-command/src/math/nushell-liniar-regression/src/xy_data.rs @@ -154,7 +154,7 @@ impl DataSet { pub fn equation_linear_regression(&self) -> String { match self.compute_linear_regression() { Ok(line) => { - return format!("d : y = a * {:.10} + {:.10}", line.slope, line.intercept); + return display_line(line); }, Err(bar) => { return format!("d : x = {:.10}", bar.x); @@ -164,10 +164,42 @@ impl DataSet { } +fn display_line(line: Line) -> String { + // d : y = a * x + b + let a: f64 = line.slope; + let b: f64 = line.intercept; + + let tolerance: f64 = 1e-10; // comparison with 10 decimals + + if a.abs() < tolerance && b.abs() < tolerance { // d : y = 0 + return format!("d : y = {:.10}", 0); + } + + + if a.abs() < tolerance && b.abs() < tolerance { // d : y = b + return format!("d : y = -{:.10}", -b); + } + + if a.abs() < tolerance && b > 0.0 { // d : y = b + return format!("d : y = {:.10}", b); + } + + + if a != 0.0 && b.abs() < tolerance { // d : y = a * x + return format!("d : y = {:.10} * x", a); + } + + // d : y = a * x + b + if b < 0.0 { + return format!("d : y = {:.10} * x - {:.10}", a, -b); + } + return format!("d : y = {:.10} * x + {:.10}", a, b); +} + + impl DataSet { - pub fn disp(&self) { + pub fn display_variables(&self) { println!("\"{}\": {:?}", self.x_name, self.x_values); println!("\"{}\": {:?}", self.y_name, self.y_values); } - -} \ No newline at end of file +}