├── .gitignore ├── src ├── 2b2.txt ├── m1.txt ├── 4b5.txt ├── rrefex.txt ├── main.rs └── lib.rs ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/2b2.txt: -------------------------------------------------------------------------------- 1 | 3 5 2 | 4 9 -------------------------------------------------------------------------------- /src/m1.txt: -------------------------------------------------------------------------------- 1 | 1.0 2.0 3.0 2 | 4.0 5.0 6.0 3 | 7.0 8.0 9.0 -------------------------------------------------------------------------------- /src/4b5.txt: -------------------------------------------------------------------------------- 1 | 2 4 5 8 3 2 | 1 6 9 1 0 3 | 5 2 3 1 7 4 | 4 5 8 3 6 5 | -------------------------------------------------------------------------------- /src/rrefex.txt: -------------------------------------------------------------------------------- 1 | 10001.4527027027027026 2 | 01000 3 | 00101.364864864864864 4 | 00010.3310810810810809 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linalg" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linear-rustgebra 2 | linear algebra library written entirely in rust 3 | 4 | youtube video: https://youtu.be/TTumOVDSSzk 5 | 6 | this shouldnt be used in any serious setting since it is very slow because of my use of Vec>. the main purpose of this project was to program some linear algebra algorithms. 7 | 8 | a faster solution would be to use an array of size length*width. 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cfg-if" 7 | version = "1.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 10 | 11 | [[package]] 12 | name = "getrandom" 13 | version = "0.2.10" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 16 | dependencies = [ 17 | "cfg-if", 18 | "libc", 19 | "wasi", 20 | ] 21 | 22 | [[package]] 23 | name = "libc" 24 | version = "0.2.147" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 27 | 28 | [[package]] 29 | name = "linalg" 30 | version = "0.1.0" 31 | dependencies = [ 32 | "rand", 33 | ] 34 | 35 | [[package]] 36 | name = "ppv-lite86" 37 | version = "0.2.17" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 40 | 41 | [[package]] 42 | name = "rand" 43 | version = "0.8.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 46 | dependencies = [ 47 | "libc", 48 | "rand_chacha", 49 | "rand_core", 50 | ] 51 | 52 | [[package]] 53 | name = "rand_chacha" 54 | version = "0.3.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 57 | dependencies = [ 58 | "ppv-lite86", 59 | "rand_core", 60 | ] 61 | 62 | [[package]] 63 | name = "rand_core" 64 | version = "0.6.4" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 67 | dependencies = [ 68 | "getrandom", 69 | ] 70 | 71 | [[package]] 72 | name = "wasi" 73 | version = "0.11.0+wasi-snapshot-preview1" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 76 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use linalg::Matrix; 2 | 3 | fn main() { 4 | // let m1 = Matrix::from_string("1 2 3 ; 4 5 6"); 5 | // println!("{m1}"); 6 | // let m2 = Matrix::from_string("7 8 9; 10 11 12"); 7 | // println!("{m2}"); 8 | // let m3 = m1.combine(m2, |a, b| (a * b)); 9 | // println!("{m3}"); 10 | 11 | // let mut m: Matrix = Matrix::new(3,3); 12 | // m.identity(); 13 | 14 | // let mut mcpy = m.copy(); 15 | // mcpy.apply(|x| x+3.0); 16 | // println!("{mcpy}"); 17 | 18 | // println!("{m}"); 19 | 20 | // let m1 = Matrix::from_file("src/2b2.txt"); 21 | // m1.print(); 22 | // println!("det(m1) = {}", m1.det()); 23 | 24 | // let mut m2 = Matrix::from_file("src/m1.txt"); 25 | // m2.apply(|i| i*2.0); 26 | // m2.print(); 27 | // println!("det(m2) = {}", m2.det()); 28 | 29 | // let m3 = Matrix::from_string("1 2 3 ; 4 5 6 ; 7 8 9"); 30 | // m3.print(); 31 | // println!("det(m3) = {}", m3.det()); 32 | 33 | // let m4 = Matrix::from_string("9 8 4 ; 2 3 7; 4 1 1"); 34 | // m4.print(); 35 | // println!("det(m4) = {}", m4.det()); 36 | 37 | // let m34 = m4.dot(m3); 38 | // println!("m4 dot m3:"); 39 | // m34.print(); 40 | 41 | // let mut m4t = m4.transpose(); 42 | // println!("testing apply"); 43 | // m4t.print(); 44 | // m4t.apply(|x| x+99.0); 45 | // m4t.print(); 46 | 47 | // let m5 = Matrix::from_string("3 0 2 ; 2 0 -2 ; 0 1 1 "); 48 | // m5.print(); 49 | 50 | // let m5i = m5.inverse(); 51 | // println!("inverse of m5:"); 52 | // m5i.print(); 53 | 54 | // // let mut m6 = Matrix::from_string("0 1 2 ; 0 3 1 ; 5 2 2"); 55 | let mut m6 = Matrix::from_string( 56 | "5 -6 -7 7 ; 57 | 3 -2 5 -17 ; 58 | 2 4 -3 29", 59 | ); 60 | m6.print(); 61 | println!("Row Reduce Echelon Form calculation:"); 62 | m6.rref(); 63 | m6.print(); 64 | 65 | let mut m7 = Matrix::from_file("src/4b5.txt"); 66 | m7.print(); 67 | println!("{:?}", m7); 68 | println!("Row Reduce Echelon Form calculation:"); 69 | m7.rref(); 70 | m7.print(); 71 | 72 | let m1 = Matrix::from_string("1 2 3; 4 5 6; 7 8 9"); 73 | let m2 = Matrix::from_string("1; 2; 3"); 74 | m1.dot(m2).print(); 75 | } 76 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Index, IndexMut}; 2 | use std::{fmt::Display, fs}; 3 | 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub struct Matrix { 6 | pub rows: usize, 7 | pub cols: usize, 8 | pub data: Vec, 9 | } 10 | 11 | impl Matrix { 12 | pub fn new(rows: usize, cols: usize) -> Self { 13 | Self { 14 | rows, 15 | cols, 16 | data: vec![0.0; rows * cols], 17 | } 18 | } 19 | 20 | pub fn from_file(path: &str) -> Self { 21 | let content = fs::read_to_string(path).unwrap_or_else(|e| panic!("{e}")); 22 | let mut data: Vec = Vec::new(); 23 | let mut cols: usize = 0; 24 | let mut count: usize = 0; 25 | 26 | for r in content.lines() { 27 | let entries: Vec<&str> = r.split_whitespace().collect(); 28 | let c = entries.len(); 29 | if count > 0 && cols != c { 30 | panic!("Columns don't match"); 31 | } 32 | cols = c; 33 | count += 1; 34 | 35 | let temp: Vec = entries 36 | .iter() 37 | .map(|ent| ent.parse::().unwrap()) 38 | .collect(); 39 | 40 | for item in temp { 41 | data.push(item); 42 | } 43 | } 44 | 45 | Self { 46 | rows: content.lines().collect::>().len(), 47 | cols, 48 | data, 49 | } 50 | } 51 | 52 | pub fn from_string(input: &str) -> Self { 53 | let mut data: Vec = Vec::new(); 54 | let rows: Vec<&str> = input.split(';').collect(); 55 | let mut cols: usize = 0; 56 | let mut count: usize = 0; 57 | 58 | for r in &rows { 59 | let entries: Vec<&str> = r.split_whitespace().collect(); 60 | let c = entries.len(); 61 | if count > 0 && cols != c { 62 | panic!("Columns don't match"); 63 | } 64 | cols = c; 65 | count += 1; 66 | 67 | let temp: Vec = entries 68 | .iter() 69 | .map(|ent| ent.parse::().unwrap()) 70 | .collect(); 71 | 72 | for item in temp { 73 | data.push(item); 74 | } 75 | } 76 | 77 | Self { 78 | rows: rows.len(), 79 | cols, 80 | data, 81 | } 82 | } 83 | 84 | pub fn copy(&self) -> Self { 85 | let mut n_data: Vec = Vec::new(); 86 | 87 | self.data.iter().for_each(|elem| n_data.push(*elem)); 88 | 89 | Self { 90 | rows: self.rows, 91 | cols: self.cols, 92 | data: n_data, 93 | } 94 | } 95 | 96 | pub fn print(&self) { 97 | for r in 0..self.rows { 98 | print!("["); 99 | for c in 0..self.cols { 100 | if c == self.cols - 1 { 101 | print!("{:.3}", self[r][c]); 102 | } else { 103 | print!("{:.3} ", self[r][c]); 104 | } 105 | } 106 | println!("]"); 107 | } 108 | } 109 | 110 | pub fn identity(&mut self) { 111 | if self.rows != self.cols { 112 | panic!("Not a square matrix."); 113 | } 114 | for r in 0..self.rows { 115 | self[r][r] = 1.0; 116 | } 117 | } 118 | 119 | pub fn apply(&mut self, f: impl Fn(f64) -> f64) { 120 | self.data = self.data.iter().map(|elem| f(*elem)).collect() 121 | } 122 | 123 | pub fn combine(&self, b: Self, f: impl Fn(f64, f64) -> f64) -> Self { 124 | if self.rows != b.rows || self.cols != b.cols { 125 | panic!("Matrices must be of the same size."); 126 | } 127 | let mut new_matrix = Self::new(self.rows, self.cols); 128 | new_matrix.data = self 129 | .data 130 | .iter() 131 | .zip(b.data.iter()) 132 | .map(|(a, b)| f(*a, *b)) 133 | .collect(); 134 | new_matrix 135 | } 136 | 137 | pub fn dot(&self, b: Self) -> Self { 138 | if self.cols != b.rows { 139 | panic!( 140 | "Dimensions not matched. M1 is {} by {}, M2 is {} by {}.", 141 | self.rows, self.cols, b.rows, b.cols 142 | ); 143 | } 144 | let mut dp = Self::new(self.rows, b.cols); 145 | for i in 0..self.rows { 146 | for j in 0..b.cols { 147 | let mut sum = 0.0; 148 | for k in 0..b.rows { 149 | sum += self[i][k] * b[k][j]; 150 | } 151 | dp[i][j] = sum; 152 | } 153 | } 154 | dp 155 | } 156 | 157 | pub fn rref(&mut self) { 158 | if self[0][0] == 0.0 { 159 | self.swap_rows(0); 160 | } 161 | let mut lead: usize = 0; 162 | let rows = self.rows; 163 | while lead < rows { 164 | for r in 0..rows { 165 | let div = self[lead][lead]; 166 | let mult = self[r][lead] / div; 167 | 168 | if r == lead { 169 | // self[lead] = self[lead].iter().map(|entry| entry / div).collect::>(); 170 | self[lead].iter_mut().for_each(|elem| *elem = (*elem) / div); 171 | } else { 172 | for c in 0..self.cols { 173 | self[r][c] -= self[lead][c] * mult; 174 | } 175 | } 176 | } 177 | lead += 1; 178 | } 179 | self.correct(); 180 | } 181 | 182 | pub fn cofactor(&self, expanded_row: usize, j: usize) -> f64 { 183 | let mut cut: Vec> = Vec::new(); 184 | for r in 0..self.rows { 185 | if r == expanded_row { 186 | continue; 187 | } 188 | let mut v: Vec = Vec::new(); 189 | for c in 0..self.cols { 190 | if c == j { 191 | continue; 192 | } 193 | v.push(self[r][c]); 194 | } 195 | cut.push(v); 196 | } 197 | let flattened = cut.clone().into_iter().flatten().collect(); 198 | let n_r = cut.len(); 199 | let n_c = cut[0].len(); 200 | let minor = Self { 201 | rows: n_r, 202 | cols: n_c, 203 | data: flattened, 204 | } 205 | .det(); 206 | let base: i32 = -1; 207 | minor * f64::from(base.pow((expanded_row + j) as u32)) 208 | } 209 | 210 | pub fn det(&self) -> f64 { 211 | if self.rows != self.cols { 212 | panic!( 213 | "Determinant requires matrix to be a square. Input matrix was {:?}.", 214 | self 215 | ); 216 | } 217 | if self.rows == 2 && self.cols == 2 { 218 | self[0][0] * self[1][1] - self[0][1] * self[1][0] 219 | } else { 220 | let row: usize = 1; 221 | let mut det = 0.0; 222 | 223 | for j in 0..self[row].len() { 224 | det += self.cofactor(row, j) * self[row][j]; 225 | } 226 | det 227 | } 228 | } 229 | 230 | pub fn transpose(&self) -> Self { 231 | let mut t = Self::new(self.cols, self.rows); 232 | for i in 0..self.rows { 233 | for j in 0..self.cols { 234 | t[j][i] = self[i][j]; 235 | } 236 | } 237 | t 238 | } 239 | 240 | pub fn trace(&self) -> f64 { 241 | if self.rows != self.cols { 242 | panic!( 243 | "Trace requires matrix to be square. Input matrix was {:?}.", 244 | self 245 | ); 246 | } 247 | let mut t: f64 = 0.0; 248 | for i in 0..self.rows { 249 | t += self[i][i]; 250 | } 251 | t 252 | } 253 | 254 | pub fn inverse(&self) -> Self { 255 | let d = self.det(); 256 | if d == 0.0 { 257 | panic!("Determinant is 0. No inverse."); 258 | } 259 | 260 | let mut inv = Self::new(self.rows, self.cols); 261 | 262 | for row in 0..self.rows { 263 | for col in 0..self.cols { 264 | inv[row][col] = self.cofactor(row, col); 265 | } 266 | } 267 | 268 | inv.correct(); 269 | inv = inv.transpose(); 270 | inv.apply(|x| x / d); 271 | inv 272 | } 273 | 274 | fn swap_rows(&mut self, row: usize) { 275 | let mut n_r = 0; 276 | for r in 0..self.rows { 277 | if self[r][0] > 0.0 { 278 | n_r = r; 279 | break; 280 | } 281 | } 282 | let temp: Vec = self[row].to_vec(); 283 | for c in 0..self.cols { 284 | self[row][c] = self[n_r][c]; 285 | self[n_r][c] = temp[n_r * self.cols + c]; 286 | } 287 | } 288 | 289 | fn correct(&mut self) { 290 | for row in 0..self.rows { 291 | for col in 0..self.cols { 292 | let elem = self[row][col]; 293 | if elem == -0.0 { 294 | self[row][col] = 0.0; 295 | } 296 | let floored = elem.floor(); 297 | if elem - floored > 0.9999999 { 298 | self[row][col] = elem.round(); 299 | } 300 | if elem > 0.0 && elem < 0.000001 { 301 | self[row][col] = 0.0; 302 | } 303 | if elem < 0.0 && elem > -0.00001 { 304 | self[row][col] = 0.0; 305 | } 306 | } 307 | } 308 | } 309 | } 310 | 311 | impl Display for Matrix { 312 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 313 | for r in 0..self.rows { 314 | write!(f, "[")?; 315 | for c in 0..self.cols { 316 | if c == self.cols - 1 { 317 | write!(f, "{:.3}", self[r][c])?; 318 | } else { 319 | write!(f, "{:.3} ", self[r][c])?; 320 | } 321 | } 322 | writeln!(f, "]")?; 323 | } 324 | 325 | Ok(()) 326 | } 327 | } 328 | 329 | impl Index for Matrix { 330 | type Output = [f64]; 331 | 332 | fn index(&self, index: usize) -> &Self::Output { 333 | &self.data[index * self.cols..(index + 1) * self.cols] 334 | } 335 | } 336 | 337 | impl IndexMut for Matrix { 338 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 339 | &mut self.data[index * self.cols..(index + 1) * self.cols] 340 | } 341 | } 342 | 343 | #[cfg(test)] 344 | mod tests { 345 | use super::*; 346 | 347 | #[test] 348 | fn test_from_string() { 349 | let m = Matrix::from_string("1 2 3 ; 4 5 6"); 350 | let expected = Matrix { 351 | rows: 2, 352 | cols: 3, 353 | data: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 354 | }; 355 | 356 | assert!(m == expected); 357 | } 358 | 359 | #[test] 360 | fn test_display() { 361 | let m = Matrix::from_string("1 2 3 ; 4 5 6"); 362 | 363 | assert_eq!("[1.0, 2.0, 3.0]\n[4.0, 5.0, 6.0]\n", m.to_string()) 364 | } 365 | } 366 | --------------------------------------------------------------------------------