From 45f147e6518f3f5c632c987663921b859847ac85 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 04:05:27 +0100 Subject: [PATCH 01/20] Add PCA examples --- examples/pca.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/pca.rs diff --git a/examples/pca.rs b/examples/pca.rs new file mode 100644 index 0000000..f4bccfe --- /dev/null +++ b/examples/pca.rs @@ -0,0 +1,47 @@ +use rustframe::compute::models::pca::PCA; +use rustframe::matrix::Matrix; + +/// Two dimensionality reduction examples using PCA. +/// +/// Example 1 reduces 3D sensor readings to two components. +/// Example 2 compresses a small four-feature dataset. +fn main() { + sensor_demo(); + println!("\n-----\n"); + finance_demo(); +} + +fn sensor_demo() { + println!("Example 1: 3D sensor data"); + + // Ten 3D observations from an accelerometer + let raw = vec![ + 2.5, 2.4, 0.5, 0.5, 0.7, 1.5, 2.2, 2.9, 0.7, 1.9, 2.2, 1.0, 3.1, 3.0, 0.6, 2.3, 2.7, 0.9, + 2.0, 1.6, 1.1, 1.0, 1.1, 1.9, 1.5, 1.6, 2.2, 1.1, 0.9, 2.1, + ]; + let x = Matrix::from_rows_vec(raw, 10, 3); + + let pca = PCA::fit(&x, 2, 0); + let reduced = pca.transform(&x); + + println!("Components: {:?}", pca.components.data()); + println!("First row -> {:.2?}", [reduced[(0, 0)], reduced[(0, 1)]]); +} + +fn finance_demo() { + println!("Example 2: 4D finance data"); + + // Four daily percentage returns of different stocks + let raw = vec![ + 0.2, 0.1, -0.1, 0.0, 0.3, 0.2, -0.2, 0.1, 0.1, 0.0, -0.1, -0.1, 0.4, 0.3, -0.3, 0.2, 0.0, + -0.1, 0.1, -0.1, + ]; + let x = Matrix::from_rows_vec(raw, 5, 4); + + // Keep two principal components + let pca = PCA::fit(&x, 2, 0); + let reduced = pca.transform(&x); + + println!("Reduced shape: {:?}", reduced.shape()); + println!("First row -> {:.2?}", [reduced[(0, 0)], reduced[(0, 1)]]); +} \ No newline at end of file From 6b580ec5eb39d8f82173341a5fca1335981d03b2 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 04:05:43 +0100 Subject: [PATCH 02/20] Add logistic regression examples --- examples/logistic_regression.rs | 73 +++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 examples/logistic_regression.rs diff --git a/examples/logistic_regression.rs b/examples/logistic_regression.rs new file mode 100644 index 0000000..1015bef --- /dev/null +++ b/examples/logistic_regression.rs @@ -0,0 +1,73 @@ +use rustframe::compute::models::logreg::LogReg; +use rustframe::matrix::Matrix; + +/// Two binary classification demos using logistic regression. +/// +/// Example 1 predicts exam success from hours studied. +/// Example 2 predicts whether an online shopper will make a purchase. +fn main() { + student_passing_example(); + println!("\n-----\n"); + purchase_prediction_example(); +} + +fn student_passing_example() { + println!("Example 1: exam pass prediction"); + + // Hours studied for each student + let hours = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; + // 0 = fail, 1 = pass + let passed = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0]; + + let x = Matrix::from_vec(hours.clone(), hours.len(), 1); + let y = Matrix::from_vec(passed.clone(), passed.len(), 1); + + let mut model = LogReg::new(1); + model.fit(&x, &y, 0.1, 10000); + + let preds = model.predict(&x); + println!("Hours -> pred | actual"); + for i in 0..x.rows() { + println!( + "{:>2} -> {} | {}", + hours[i] as i32, + preds[(i, 0)] as i32, + passed[i] as i32 + ); + } + + // Probability estimate for a new student + let new_student = Matrix::from_vec(vec![5.5], 1, 1); + let p = model.predict_proba(&new_student); + println!("Probability of passing with 5.5h study: {:.2}", p[(0, 0)]); +} + +fn purchase_prediction_example() { + println!("Example 2: purchase likelihood"); + + // minutes on site, pages viewed -> made a purchase? + let raw_x = vec![1.0, 2.0, 3.0, 1.0, 2.0, 4.0, 5.0, 5.0, 3.5, 2.0, 6.0, 6.0]; + let bought = vec![0.0, 0.0, 0.0, 1.0, 0.0, 1.0]; + + let x = Matrix::from_rows_vec(raw_x, 6, 2); + let y = Matrix::from_vec(bought.clone(), bought.len(), 1); + + let mut model = LogReg::new(2); + model.fit(&x, &y, 0.05, 20000); + + let preds = model.predict(&x); + println!("time, pages -> pred | actual"); + for i in 0..x.rows() { + println!( + "{:>4}m, {:>2} -> {} | {}", + x[(i, 0)], + x[(i, 1)] as i32, + preds[(i, 0)] as i32, + bought[i] as i32 + ); + } + + let new_visit = Matrix::from_rows_vec(vec![4.0, 4.0], 1, 2); + let p = model.predict_proba(&new_visit); + println!("Prob of purchase for 4min/4pages: {:.2}", p[(0, 0)]); +} From fe9498963d2c9e2a9227dd6184a55aee5982b3a7 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 04:05:56 +0100 Subject: [PATCH 03/20] Add linear regression examples --- examples/linear_regression.rs | 83 +++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 examples/linear_regression.rs diff --git a/examples/linear_regression.rs b/examples/linear_regression.rs new file mode 100644 index 0000000..0ad4afe --- /dev/null +++ b/examples/linear_regression.rs @@ -0,0 +1,83 @@ +use rustframe::compute::models::linreg::LinReg; +use rustframe::matrix::Matrix; + +/// Two quick linear regression demonstrations. +/// +/// Example 1 fits a model to predict house price from floor area. +/// Example 2 adds number of bedrooms as a second feature. +fn main() { + example_one_feature(); + println!("\n-----\n"); + example_two_features(); +} + +/// Price ~ floor area +fn example_one_feature() { + println!("Example 1: predict price from floor area only"); + + // Square meters of floor area for a few houses + let sizes = vec![50.0, 60.0, 70.0, 80.0, 90.0, 100.0]; + // Thousands of dollars in sale price + let prices = vec![150.0, 180.0, 210.0, 240.0, 270.0, 300.0]; + + // Each row is a sample with one feature + let x = Matrix::from_vec(sizes.clone(), sizes.len(), 1); + let y = Matrix::from_vec(prices.clone(), prices.len(), 1); + + // Train with a small learning rate + let mut model = LinReg::new(1); + model.fit(&x, &y, 0.0005, 20000); + + let preds = model.predict(&x); + println!("Size (m^2) -> predicted price (k) vs actual"); + for i in 0..x.rows() { + println!( + "{:>3} -> {:>6.1} | {:>6.1}", + sizes[i], + preds[(i, 0)], + prices[i] + ); + } + + let new_house = Matrix::from_vec(vec![120.0], 1, 1); + let pred = model.predict(&new_house); + println!("Predicted price for 120 m^2: {:.1}k", pred[(0, 0)]); +} + +/// Price ~ floor area + bedrooms +fn example_two_features() { + println!("Example 2: price from area and bedrooms"); + + // (size m^2, bedrooms) for each house + let raw_x = vec![ + 50.0, 2.0, 70.0, 2.0, 90.0, 3.0, 110.0, 3.0, 130.0, 4.0, 150.0, 4.0, + ]; + let prices = vec![160.0, 195.0, 250.0, 285.0, 320.0, 350.0]; + + let x = Matrix::from_rows_vec(raw_x, 6, 2); + let y = Matrix::from_vec(prices.clone(), prices.len(), 1); + + let mut model = LinReg::new(2); + model.fit(&x, &y, 0.0001, 50000); + + let preds = model.predict(&x); + println!("size, beds -> predicted | actual (k)"); + for i in 0..x.rows() { + let size = x[(i, 0)]; + let beds = x[(i, 1)]; + println!( + "{:>3} m^2, {:>1} -> {:>6.1} | {:>6.1}", + size, + beds, + preds[(i, 0)], + prices[i] + ); + } + + let new_home = Matrix::from_rows_vec(vec![120.0, 3.0], 1, 2); + let pred = model.predict(&new_home); + println!( + "Predicted price for 120 m^2 with 3 bedrooms: {:.1}k", + pred[(0, 0)] + ); +} From ded5f1aa2914fe78ecf680b1429cd43bbabeab48 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 04:06:12 +0100 Subject: [PATCH 04/20] Add k-means examples --- examples/k_means.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/k_means.rs diff --git a/examples/k_means.rs b/examples/k_means.rs new file mode 100644 index 0000000..c1144c0 --- /dev/null +++ b/examples/k_means.rs @@ -0,0 +1,51 @@ +use rustframe::compute::models::k_means::KMeans; +use rustframe::matrix::Matrix; + +/// Two quick K-Means clustering demos. +/// +/// Example 1 groups store locations on a city map. +/// Example 2 segments customers by annual spending habits. +fn main() { + city_store_example(); + println!("\n-----\n"); + customer_spend_example(); +} + +fn city_store_example() { + println!("Example 1: store locations"); + + // (x, y) coordinates of stores around a city + let raw = vec![ + 1.0, 2.0, 1.5, 1.8, 5.0, 8.0, 8.0, 8.0, 1.0, 0.6, 9.0, 11.0, 8.0, 2.0, 10.0, 2.0, 9.0, 3.0, + ]; + let x = Matrix::from_rows_vec(raw, 9, 2); + + // Group stores into two areas + let (model, labels) = KMeans::fit(&x, 2, 100, 1e-4); + + println!("Centres: {:?}", model.centroids.data()); + println!("Labels: {:?}", labels); + + let new_points = Matrix::from_rows_vec(vec![0.0, 0.0, 8.0, 3.0], 2, 2); + let pred = model.predict(&new_points); + println!("New store assignments: {:?}", pred); +} + +fn customer_spend_example() { + println!("Example 2: customer spending"); + + // (grocery spend, electronics spend) in dollars + let raw = vec![ + 200.0, 150.0, 220.0, 170.0, 250.0, 160.0, 800.0, 750.0, 820.0, 760.0, 790.0, 770.0, + ]; + let x = Matrix::from_rows_vec(raw, 6, 2); + + let (model, labels) = KMeans::fit(&x, 2, 100, 1e-4); + + println!("Centres: {:?}", model.centroids.data()); + println!("Labels: {:?}", labels); + + let new_customers = Matrix::from_rows_vec(vec![230.0, 155.0, 810.0, 760.0], 2, 2); + let pred = model.predict(&new_customers); + println!("Cluster of new customers: {:?}", pred); +} From 733a4da3831e5ebeccba370e4587fffb51cd1273 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 10:51:35 +0100 Subject: [PATCH 05/20] Add unit test in pca.rs --- examples/pca.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/pca.rs b/examples/pca.rs index f4bccfe..4cc2fc4 100644 --- a/examples/pca.rs +++ b/examples/pca.rs @@ -44,4 +44,17 @@ fn finance_demo() { println!("Reduced shape: {:?}", reduced.shape()); println!("First row -> {:.2?}", [reduced[(0, 0)], reduced[(0, 1)]]); +} + +#[test] +fn test_sensor_demo() { + let raw = vec![ + 2.5, 2.4, 0.5, 0.5, 0.7, 1.5, 2.2, 2.9, 0.7, 1.9, 2.2, 1.0, 3.1, 3.0, 0.6, 2.3, 2.7, 0.9, + 2.0, 1.6, 1.1, 1.0, 1.1, 1.9, 1.5, 1.6, 2.2, 1.1, 0.9, 2.1, + ]; + let x = Matrix::from_rows_vec(raw, 10, 3); + let pca = PCA::fit(&x, 2, 0); + let reduced = pca.transform(&x); + assert_eq!(reduced.rows(), 10); + assert_eq!(reduced.cols(), 2); } \ No newline at end of file From 45ec754d47a7594b92815e82ac72ca958f61457f Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:21:27 +0100 Subject: [PATCH 06/20] add test as examples --- examples/logistic_regression.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/examples/logistic_regression.rs b/examples/logistic_regression.rs index 1015bef..652f811 100644 --- a/examples/logistic_regression.rs +++ b/examples/logistic_regression.rs @@ -71,3 +71,31 @@ fn purchase_prediction_example() { let p = model.predict_proba(&new_visit); println!("Prob of purchase for 4min/4pages: {:.2}", p[(0, 0)]); } + +#[test] +fn test_student_passing_example() { + let hours = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; + let passed = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0]; + let x = Matrix::from_vec(hours.clone(), hours.len(), 1); + let y = Matrix::from_vec(passed.clone(), passed.len(), 1); + let mut model = LogReg::new(1); + model.fit(&x, &y, 0.1, 10000); + let preds = model.predict(&x); + for i in 0..y.rows() { + assert_eq!(preds[(i, 0)], passed[i]); + } +} + +#[test] +fn test_purchase_prediction_example() { + let raw_x = vec![1.0, 2.0, 3.0, 1.0, 2.0, 4.0, 5.0, 5.0, 3.5, 2.0, 6.0, 6.0]; + let bought = vec![0.0, 0.0, 0.0, 1.0, 0.0, 1.0]; + let x = Matrix::from_rows_vec(raw_x, 6, 2); + let y = Matrix::from_vec(bought.clone(), bought.len(), 1); + let mut model = LogReg::new(2); + model.fit(&x, &y, 0.05, 20000); + let preds = model.predict(&x); + for i in 0..y.rows() { + assert_eq!(preds[(i, 0)], bought[i]); + } +} \ No newline at end of file From b0d8050b110d4623c102c8107994551d6f98bbaf Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 13:26:44 +0100 Subject: [PATCH 07/20] add test as examples --- examples/k_means.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/examples/k_means.rs b/examples/k_means.rs index c1144c0..4bc1144 100644 --- a/examples/k_means.rs +++ b/examples/k_means.rs @@ -49,3 +49,17 @@ fn customer_spend_example() { let pred = model.predict(&new_customers); println!("Cluster of new customers: {:?}", pred); } + +#[test] +fn k_means_store_locations() { + let raw = vec![ + 1.0, 2.0, 1.5, 1.8, 5.0, 8.0, 8.0, 8.0, 1.0, 0.6, 9.0, 11.0, 8.0, 2.0, 10.0, 2.0, 9.0, 3.0, + ]; + let x = Matrix::from_rows_vec(raw, 9, 2); + let (model, labels) = KMeans::fit(&x, 2, 100, 1e-4); + assert_eq!(labels.len(), 9); + assert_eq!(model.centroids.rows(), 2); + let new_points = Matrix::from_rows_vec(vec![0.0, 0.0, 8.0, 3.0], 2, 2); + let pred = model.predict(&new_points); + assert_eq!(pred.len(), 2); +} From d0f9e80dfcc59afa0ecfa193b53d316d5f12a303 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:38:27 +0100 Subject: [PATCH 08/20] add test as examples --- examples/linear_regression.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/examples/linear_regression.rs b/examples/linear_regression.rs index 0ad4afe..033d96a 100644 --- a/examples/linear_regression.rs +++ b/examples/linear_regression.rs @@ -81,3 +81,38 @@ fn example_two_features() { pred[(0, 0)] ); } + +#[test] +fn test_linear_regression_one_feature() { + let sizes = vec![50.0, 60.0, 70.0, 80.0, 90.0, 100.0]; + let prices = vec![150.0, 180.0, 210.0, 240.0, 270.0, 300.0]; + let scaled: Vec = sizes.iter().map(|s| s / 100.0).collect(); + let x = Matrix::from_vec(scaled, sizes.len(), 1); + let y = Matrix::from_vec(prices.clone(), prices.len(), 1); + let mut model = LinReg::new(1); + model.fit(&x, &y, 0.1, 2000); + let preds = model.predict(&x); + for i in 0..y.rows() { + assert!((preds[(i, 0)] - prices[i]).abs() < 1.0); + } +} + +#[test] +fn test_linear_regression_two_features() { + let raw_x = vec![ + 50.0, 2.0, 70.0, 2.0, 90.0, 3.0, 110.0, 3.0, 130.0, 4.0, 150.0, 4.0, + ]; + let prices = vec![170.0, 210.0, 270.0, 310.0, 370.0, 410.0]; + let scaled_x: Vec = raw_x + .chunks(2) + .flat_map(|pair| vec![pair[0] / 100.0, pair[1]]) + .collect(); + let x = Matrix::from_rows_vec(scaled_x, 6, 2); + let y = Matrix::from_vec(prices.clone(), prices.len(), 1); + let mut model = LinReg::new(2); + model.fit(&x, &y, 0.01, 50000); + let preds = model.predict(&x); + for i in 0..y.rows() { + assert!((preds[(i, 0)] - prices[i]).abs() < -1.0); + } +} From 1192a78955f0226bdf2d846b636e59d2a450575d Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:38:53 +0100 Subject: [PATCH 09/20] Add example demos to README.md --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index f37f334..a382287 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,24 @@ E.g. to run the `game_of_life` example: cargo run --example game_of_life ``` +More demos: + +```bash +cargo run --example linear_regression +cargo run --example logistic_regression +cargo run --example k_means +cargo run --example pca +``` + +To simply list all available examples, you can run: + +```bash +# this technically raises an error, but it will list all examples +cargo run --example +``` + +Each demo runs a couple of mini-scenarios showcasing the APIs. + ### Running benchmarks To run the benchmarks, use: From 44ff16a0bbd37cb8dd8b677d89d8cf76db7933f2 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:30:03 +0100 Subject: [PATCH 10/20] Refactor Game of Life example to support debug mode and improve board printing --- examples/game_of_life.rs | 49 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index eb88afd..020a0a5 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -1,11 +1,26 @@ +//! Conway's Game of Life Example +//! This example implements Conway's Game of Life using a `BoolMatrix` to represent the game board. +//! It demonstrates matrix operations like shifting, counting neighbors, and applying game rules. +//! The game runs in a loop, updating the board state and printing it to the console. +//! To modify the behaviour of the example, please change the constants at the top of this file. +//! By default, + use rand::{self, Rng}; use rustframe::matrix::{BoolMatrix, BoolOps, IntMatrix, Matrix}; use std::{thread, time}; -const BOARD_SIZE: usize = 50; // Size of the board (50x50) -const TICK_DURATION_MS: u64 = 10; // Milliseconds per frame +const BOARD_SIZE: usize = 20; // Size of the board (50x50) +const MAX_FRAMES: u32 = 1000; + +const TICK_DURATION_MS: u64 = 0; // Milliseconds per frame +const SKIP_FRAMES: u32 = 1; +const PRINT_BOARD: bool = true; // Set to false to disable printing the board fn main() { + let args = std::env::args().collect::>(); + let debug_mode = args.contains(&"--debug".to_string()); + let print_mode = if debug_mode { false } else { PRINT_BOARD }; + // Initialize the game board. // This demonstrates `BoolMatrix::from_vec`. let mut current_board = @@ -24,20 +39,12 @@ fn main() { let mut print_bool_int = 0; loop { - // print!("{}[2J", 27 as char); // Clear screen and move cursor to top-left - // if print_board_bool { - if print_bool_int % 10 == 0 { - print!("{}[2J", 27 as char); - println!("Conway's Game of Life - Generation: {}", generation_count); + if print_bool_int % SKIP_FRAMES == 0 { + print_board(¤t_board, generation_count, print_mode); - print_board(¤t_board); - println!("Alive cells: {}", ¤t_board.count()); - - // print_board_bool = false; print_bool_int = 0; } else { - // print_board_bool = true; print_bool_int += 1; } // `current_board.count()` demonstrates a method from `BoolOps`. @@ -71,10 +78,10 @@ fn main() { generation_count += 1; thread::sleep(time::Duration::from_millis(TICK_DURATION_MS)); - // if generation_count > 500 { // Optional limit - // println!("\nReached generation limit."); - // break; - // } + if (MAX_FRAMES > 0) && (generation_count > MAX_FRAMES) { + println!("\nReached generation limit."); + break; + } } } @@ -82,7 +89,13 @@ fn main() { /// /// - `board`: A reference to the `BoolMatrix` representing the current game state. /// This function demonstrates `board.rows()`, `board.cols()`, and `board[(r, c)]` (Index trait). -fn print_board(board: &BoolMatrix) { +fn print_board(board: &BoolMatrix, generation_count: u32, print_mode: bool) { + if !print_mode { + return; + } + + print!("{}[2J", 27 as char); + println!("Conway's Game of Life - Generation: {}", generation_count); let mut print_str = String::new(); print_str.push_str("+"); for _ in 0..board.cols() { @@ -107,6 +120,8 @@ fn print_board(board: &BoolMatrix) { } print_str.push_str("+\n\n"); print!("{}", print_str); + + println!("Alive cells: {}", board.count()); } /// Helper function to create a shifted version of the game board. From 26213b28d61c4bf13e4e249f3944a0fdcb27d477 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:31:08 +0100 Subject: [PATCH 11/20] Refactor GitHub Actions workflow to streamline unit tests and add example tests --- .github/workflows/run-unit-tests.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 10eb847..78ed71c 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -12,14 +12,12 @@ concurrency: jobs: pick-runner: - if: github.event.pull_request.draft == false runs-on: ubuntu-latest outputs: runner: ${{ steps.choose.outputs.use-runner }} steps: - - uses: actions/checkout@v4 - id: choose uses: ./.github/actions/runner-fallback @@ -27,7 +25,6 @@ jobs: primary-runner: "self-hosted" fallback-runner: "ubuntu-latest" github-token: ${{ secrets.CUSTOM_GH_TOKEN }} - run-unit-tests: needs: pick-runner @@ -56,6 +53,12 @@ jobs: - name: Test docs generation run: cargo doc --no-deps --release + - name: Test examples + run: cargo test --examples --release + + - name: Cargo test all targets + run: cargo test --all-targets --release + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: From 72d02e2336fb607ba96ef974b311fa5230527da3 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:33:19 +0100 Subject: [PATCH 12/20] Add script to run all example programs with debug mode --- .github/scripts/run_examples.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/scripts/run_examples.sh diff --git a/.github/scripts/run_examples.sh b/.github/scripts/run_examples.sh new file mode 100644 index 0000000..33a2fa5 --- /dev/null +++ b/.github/scripts/run_examples.sh @@ -0,0 +1,16 @@ +cargo build --release --examples + +for ex in examples/*.rs; do + name=$(basename "$ex" .rs) + echo + echo "🟡 Running example: $name" + + if ! cargo run --release --example "$name" -- --debug; then + echo + echo "❌ Example '$name' failed. Aborting." + exit 1 + fi +done + +echo +echo "✅ All examples ran successfully." From 0ce970308b0b3529a2ed92d17429504067b01267 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:33:28 +0100 Subject: [PATCH 13/20] Add step to run all examples in debug mode during unit tests --- .github/workflows/run-unit-tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 78ed71c..0f4ae0b 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -56,6 +56,14 @@ jobs: - name: Test examples run: cargo test --examples --release + - name: Run all examples + run: | + for example in examples/*.rs; do + name=$(basename "$example" .rs) + echo "Running example: $name" + cargo run --release --example "$name" -- --debug || exit 1 + done + - name: Cargo test all targets run: cargo test --all-targets --release From 3935e80be6a1288f663b45e6a83192c32c787e91 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 20:35:47 +0100 Subject: [PATCH 14/20] Fix typo in assertion --- examples/linear_regression.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/linear_regression.rs b/examples/linear_regression.rs index 033d96a..267d805 100644 --- a/examples/linear_regression.rs +++ b/examples/linear_regression.rs @@ -113,6 +113,6 @@ fn test_linear_regression_two_features() { model.fit(&x, &y, 0.01, 50000); let preds = model.predict(&x); for i in 0..y.rows() { - assert!((preds[(i, 0)] - prices[i]).abs() < -1.0); + assert!((preds[(i, 0)] - prices[i]).abs() < 1.0); } } From ab3509fef48e9bdce6c0ad83d346e18b4fcce5b1 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:04:34 +0100 Subject: [PATCH 15/20] Added examples/stats_overview --- examples/stats_overview.rs | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 examples/stats_overview.rs diff --git a/examples/stats_overview.rs b/examples/stats_overview.rs new file mode 100644 index 0000000..fe25ba7 --- /dev/null +++ b/examples/stats_overview.rs @@ -0,0 +1,93 @@ +use rustframe::compute::stats::{ + chi2_test, covariance, covariance_matrix, mean, median, pearson, percentile, stddev, t_test, +}; +use rustframe::matrix::{Axis, Matrix}; + +/// Demonstrates some of the statistics utilities in Rustframe. +/// +/// The example is split into three parts: +/// 1. Basic descriptive statistics on a small data set. +/// 2. Covariance and correlation calculations. +/// 3. Simple inferential tests (t-test and chi-square). +fn main() { + descriptive_demo(); + println!("\n-----\n"); + correlation_demo(); + println!("\n-----\n"); + inferential_demo(); +} + +fn descriptive_demo() { + println!("Descriptive statistics\n----------------------"); + let data = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + println!("mean : {:.2}", mean(&data)); + println!("std dev : {:.2}", stddev(&data)); + println!("median : {:.2}", median(&data)); + println!("25th percentile: {:.2}", percentile(&data, 25.0)); +} + +fn correlation_demo() { + println!("Covariance and Correlation\n--------------------------"); + let x = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let y = Matrix::from_vec(vec![1.0, 2.0, 3.0, 5.0], 2, 2); + let cov = covariance(&x, &y); + let cov_mat = covariance_matrix(&x, Axis::Col); + let corr = pearson(&x, &y); + println!("covariance : {:.2}", cov); + println!("cov matrix : {:?}", cov_mat.data()); + println!("pearson r : {:.2}", corr); +} + +fn inferential_demo() { + println!("Inferential statistics\n----------------------"); + let s1 = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + let s2 = Matrix::from_vec(vec![6.0, 7.0, 8.0, 9.0, 10.0], 1, 5); + let (t_stat, t_p) = t_test(&s1, &s2); + println!("t statistic : {:.2}, p-value: {:.4}", t_stat, t_p); + + let observed = Matrix::from_vec(vec![12.0, 5.0, 8.0, 10.0], 2, 2); + let (chi2, chi_p) = chi2_test(&observed); + println!("chi^2 : {:.2}, p-value: {:.4}", chi2, chi_p); +} + +#[cfg(test)] +mod tests { + use super::*; + const EPS: f64 = 1e-8; + + #[test] + fn test_descriptive_demo() { + let data = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + assert!((mean(&data) - 3.0).abs() < EPS); + assert!((stddev(&data) - 1.4142135623730951).abs() < EPS); + assert!((median(&data) - 3.0).abs() < EPS); + assert!((percentile(&data, 25.0) - 2.0).abs() < EPS); + } + + #[test] + fn test_correlation_demo() { + let x = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let y = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 5.0], 2, 2); + let cov = covariance(&x, &y); + assert!((cov - 1.625).abs() < EPS); + let cov_mat = covariance_matrix(&x, Axis::Col); + assert!((cov_mat.get(0, 0) - 2.0).abs() < EPS); + assert!((cov_mat.get(1, 1) - 2.0).abs() < EPS); + let corr = pearson(&x, &y); + assert!((corr - 0.9827076298239908).abs() < 1e-6); + } + + #[test] + fn test_inferential_demo() { + let s1 = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + let s2 = Matrix::from_rows_vec(vec![6.0, 7.0, 8.0, 9.0, 10.0], 1, 5); + let (t_stat, p_value) = t_test(&s1, &s2); + assert!((t_stat + 5.0).abs() < 1e-5); + assert!(p_value > 0.0 && p_value < 1.0); + + let observed = Matrix::from_rows_vec(vec![12.0, 5.0, 8.0, 10.0], 2, 2); + let (chi2, p) = chi2_test(&observed); + assert!(chi2 > 0.0); + assert!(p > 0.0 && p < 1.0); + } +} From dccbba9d1b244117aefa80f607fce23f28e60fe6 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:05:25 +0100 Subject: [PATCH 16/20] Add examples for distribution helpers --- examples/distributions.rs | 66 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 examples/distributions.rs diff --git a/examples/distributions.rs b/examples/distributions.rs new file mode 100644 index 0000000..d1b5f08 --- /dev/null +++ b/examples/distributions.rs @@ -0,0 +1,66 @@ +use rustframe::compute::stats::{binomial_cdf, binomial_pmf, normal_cdf, normal_pdf, poisson_pmf}; +use rustframe::matrix::Matrix; + +/// Demonstrates some probability distribution helpers. +fn main() { + normal_example(); + println!("\n-----\n"); + binomial_example(); + println!("\n-----\n"); + poisson_example(); +} + +fn normal_example() { + println!("Normal distribution\n-------------------"); + let x = Matrix::from_vec(vec![0.0, 1.0], 1, 2); + let pdf = normal_pdf(x.clone(), 0.0, 1.0); + let cdf = normal_cdf(x, 0.0, 1.0); + println!("pdf : {:?}", pdf.data()); + println!("cdf : {:?}", cdf.data()); +} + +fn binomial_example() { + println!("Binomial distribution\n---------------------"); + let k = Matrix::from_vec(vec![0_u64, 1, 2], 1, 3); + let pmf = binomial_pmf(4, k.clone(), 0.5); + let cdf = binomial_cdf(4, k, 0.5); + println!("pmf : {:?}", pmf.data()); + println!("cdf : {:?}", cdf.data()); +} + +fn poisson_example() { + println!("Poisson distribution\n--------------------"); + let k = Matrix::from_vec(vec![0_u64, 1, 2], 1, 3); + let pmf = poisson_pmf(3.0, k); + println!("pmf : {:?}", pmf.data()); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_normal_example() { + let x = Matrix::from_vec(vec![0.0, 1.0], 1, 2); + let pdf = normal_pdf(x.clone(), 0.0, 1.0); + let cdf = normal_cdf(x, 0.0, 1.0); + assert!((pdf.get(0, 0) - 0.39894228).abs() < 1e-6); + assert!((cdf.get(0, 1) - 0.8413447).abs() < 1e-6); + } + + #[test] + fn test_binomial_example() { + let k = Matrix::from_vec(vec![0_u64, 1, 2], 1, 3); + let pmf = binomial_pmf(4, k.clone(), 0.5); + let cdf = binomial_cdf(4, k, 0.5); + assert!((pmf.get(0, 2) - 0.375).abs() < 1e-6); + assert!((cdf.get(0, 2) - 0.6875).abs() < 1e-6); + } + + #[test] + fn test_poisson_example() { + let k = Matrix::from_vec(vec![0_u64, 1, 2], 1, 3); + let pmf = poisson_pmf(3.0, k); + assert!((pmf.get(0, 1) - 3.0_f64 * (-3.0_f64).exp()).abs() < 1e-6); + } +} From c004bd8334cbac89bc244f2c4b2d661d37f38d4e Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:05:41 +0100 Subject: [PATCH 17/20] Add inferential statistics examples --- examples/inferential_stats.rs | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 examples/inferential_stats.rs diff --git a/examples/inferential_stats.rs b/examples/inferential_stats.rs new file mode 100644 index 0000000..ac6b9b8 --- /dev/null +++ b/examples/inferential_stats.rs @@ -0,0 +1,66 @@ +use rustframe::compute::stats::{anova, chi2_test, t_test}; +use rustframe::matrix::Matrix; + +/// Demonstrates simple inferential statistics tests. +fn main() { + t_test_demo(); + println!("\n-----\n"); + chi2_demo(); + println!("\n-----\n"); + anova_demo(); +} + +fn t_test_demo() { + println!("Two-sample t-test\n-----------------"); + let a = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + let b = Matrix::from_vec(vec![6.0, 7.0, 8.0, 9.0, 10.0], 1, 5); + let (t, p) = t_test(&a, &b); + println!("t statistic: {:.2}, p-value: {:.4}", t, p); +} + +fn chi2_demo() { + println!("Chi-square test\n---------------"); + let observed = Matrix::from_vec(vec![12.0, 5.0, 8.0, 10.0], 2, 2); + let (chi2, p) = chi2_test(&observed); + println!("chi^2: {:.2}, p-value: {:.4}", chi2, p); +} + +fn anova_demo() { + println!("One-way ANOVA\n-------------"); + let g1 = Matrix::from_vec(vec![1.0, 2.0, 3.0], 1, 3); + let g2 = Matrix::from_vec(vec![2.0, 3.0, 4.0], 1, 3); + let g3 = Matrix::from_vec(vec![3.0, 4.0, 5.0], 1, 3); + let (f, p) = anova(vec![&g1, &g2, &g3]); + println!("F statistic: {:.2}, p-value: {:.4}", f, p); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_t_test_demo() { + let a = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + let b = Matrix::from_vec(vec![6.0, 7.0, 8.0, 9.0, 10.0], 1, 5); + let (t, _p) = t_test(&a, &b); + assert!((t + 5.0).abs() < 1e-5); + } + + #[test] + fn test_chi2_demo() { + let observed = Matrix::from_vec(vec![12.0, 5.0, 8.0, 10.0], 2, 2); + let (chi2, p) = chi2_test(&observed); + assert!(chi2 > 0.0); + assert!(p > 0.0 && p < 1.0); + } + + #[test] + fn test_anova_demo() { + let g1 = Matrix::from_vec(vec![1.0, 2.0, 3.0], 1, 3); + let g2 = Matrix::from_vec(vec![2.0, 3.0, 4.0], 1, 3); + let g3 = Matrix::from_vec(vec![3.0, 4.0, 5.0], 1, 3); + let (f, p) = anova(vec![&g1, &g2, &g3]); + assert!(f > 0.0); + assert!(p > 0.0 && p < 1.0); + } +} From 38213c73c7d27bffc4b177c4f367c209de8b5f4f Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:05:56 +0100 Subject: [PATCH 18/20] Add examples for covariance and correlation --- examples/correlation.rs | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 examples/correlation.rs diff --git a/examples/correlation.rs b/examples/correlation.rs new file mode 100644 index 0000000..b873e0a --- /dev/null +++ b/examples/correlation.rs @@ -0,0 +1,45 @@ +use rustframe::compute::stats::{covariance, covariance_matrix, pearson}; +use rustframe::matrix::{Axis, Matrix}; + +/// Demonstrates covariance and correlation utilities. +fn main() { + pairwise_cov(); + println!("\n-----\n"); + matrix_cov(); +} + +fn pairwise_cov() { + println!("Covariance & Pearson r\n----------------------"); + let x = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let y = Matrix::from_vec(vec![1.0, 2.0, 3.0, 5.0], 2, 2); + println!("covariance : {:.2}", covariance(&x, &y)); + println!("pearson r : {:.3}", pearson(&x, &y)); +} + +fn matrix_cov() { + println!("Covariance matrix\n-----------------"); + let data = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let cov = covariance_matrix(&data, Axis::Col); + println!("cov matrix : {:?}", cov.data()); +} + +#[cfg(test)] +mod tests { + use super::*; + const EPS: f64 = 1e-8; + + #[test] + fn test_pairwise_cov() { + let x = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let y = Matrix::from_vec(vec![1.0, 2.0, 3.0, 5.0], 2, 2); + assert!((covariance(&x, &y) - 1.625).abs() < EPS); + assert!((pearson(&x, &y) - 0.9827076298239908).abs() < 1e-5,); + } + + #[test] + fn test_matrix_cov() { + let data = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 4.0], 2, 2); + let cov = covariance_matrix(&data, Axis::Col); + assert_eq!(cov.data(), &[2.0, 2.0, 2.0, 2.0]); + } +} From 2a99d8930c3cfa53e4f6e8c8878248a6703745ba Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:06:08 +0100 Subject: [PATCH 19/20] Add examples for descriptive stats --- examples/descriptive_stats.rs | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 examples/descriptive_stats.rs diff --git a/examples/descriptive_stats.rs b/examples/descriptive_stats.rs new file mode 100644 index 0000000..972140d --- /dev/null +++ b/examples/descriptive_stats.rs @@ -0,0 +1,56 @@ +use rustframe::compute::stats::{mean, mean_horizontal, mean_vertical, median, percentile, stddev}; +use rustframe::matrix::Matrix; + +/// Demonstrates descriptive statistics utilities. +/// +/// Part 1: simple mean/stddev/median/percentile on a vector. +/// Part 2: mean across rows and columns. +fn main() { + simple_stats(); + println!("\n-----\n"); + axis_stats(); +} + +fn simple_stats() { + println!("Basic stats\n-----------"); + let data = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + println!("mean : {:.2}", mean(&data)); + println!("stddev : {:.2}", stddev(&data)); + println!("median : {:.2}", median(&data)); + println!("90th pct. : {:.2}", percentile(&data, 90.0)); +} + +fn axis_stats() { + println!("Row/column means\n----------------"); + // 2x3 matrix + let data = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2, 3); + let v = mean_vertical(&data); // 1x3 + let h = mean_horizontal(&data); // 2x1 + println!("vertical means : {:?}", v.data()); + println!("horizontal means: {:?}", h.data()); +} + +#[cfg(test)] +mod tests { + use super::*; + const EPS: f64 = 1e-8; + + #[test] + fn test_simple_stats() { + let data = Matrix::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0], 1, 5); + assert!((mean(&data) - 3.0).abs() < EPS); + assert!((stddev(&data) - 1.4142135623730951).abs() < EPS); + assert!((median(&data) - 3.0).abs() < EPS); + assert!((percentile(&data, 90.0) - 5.0).abs() < EPS); + } + + #[test] + fn test_axis_stats() { + let data = Matrix::from_rows_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 2, 3); + let v = mean_vertical(&data); + assert_eq!(v.data(), &[2.5, 3.5, 4.5]); + let h = mean_horizontal(&data); + assert_eq!(h.data(), &[2.0, 5.0]); + } +} + From 17201b4d2913a0e5a677376925f3a67ab5d5efa4 Mon Sep 17 00:00:00 2001 From: Palash Tyagi <23239946+Magnus167@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:06:47 +0100 Subject: [PATCH 20/20] Add example commands for statistical operations in README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a382287..e6fab01 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,11 @@ cargo run --example linear_regression cargo run --example logistic_regression cargo run --example k_means cargo run --example pca +cargo run --example stats_overview +cargo run --example descriptive_stats +cargo run --example correlation +cargo run --example inferential_stats +cargo run --example distributions ``` To simply list all available examples, you can run: