diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml new file mode 100644 index 0000000..31f2f8a --- /dev/null +++ b/.github/workflows/nextest.yml @@ -0,0 +1,25 @@ +name: Rust Tests (nextest) + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + test: + name: Run cargo nextest + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Run tests + run: cargo test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0cb84d --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +target/ +Cargo.lock + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3446e45 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "matrix" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a14c311 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "matrix" +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/matrix_display/README.md b/matrix_display/README.md new file mode 100644 index 0000000..6b2ed36 --- /dev/null +++ b/matrix_display/README.md @@ -0,0 +1,25 @@ +# Projection Matrix Display + +This software is intended to provide a way to check that a 3D projection matrix is valid. + +To perform the test, place the matrix into a file called `proj`. Example: + +``` +1.0, 0.0, 0.0, 0.0 +0.0, 1.0, 0.0, 0.0 +0.0, 0.0, 1.0, 0.0 +0.0, 0.0, 0.0, 1.0 +``` + +(The above matrix will not work properly for a 3D projection but its format is correct) + +If the matrix is correct, the display will show a 3D projection of the 42AI organization's logo. + +## Controls + +- **Esc**: Quit the program +- **Arrow Up**/**W**: Move forward +- **Arrow Down**/**S**: Move backward +- **Arrow Left**/**A**: Move left +- **Arrow Right**/**D**: Move right +- **Mouse**: Look around diff --git a/matrix_display/assets/logo.png b/matrix_display/assets/logo.png new file mode 100644 index 0000000..f4a6c1a Binary files /dev/null and b/matrix_display/assets/logo.png differ diff --git a/matrix_display/assets/model.obj b/matrix_display/assets/model.obj new file mode 100644 index 0000000..03f5131 --- /dev/null +++ b/matrix_display/assets/model.obj @@ -0,0 +1,180 @@ +# Blender v2.91.2 OBJ File: '42ai_3d.blend' +# www.blender.org +mtllib 42ai_3d.mtl +o Shape +v -0.456377 -0.177362 0.100000 +v -0.130392 0.386972 0.100000 +v 0.195574 -0.177362 0.100000 +v 0.064101 -0.177362 0.100000 +v 0.000605 -0.064495 0.100000 +v -0.000578 -0.064495 0.100000 +v -0.130392 0.161238 0.100000 +v -0.260225 -0.064495 0.100000 +v -0.130640 -0.064495 0.100000 +v -0.065714 -0.177362 0.100000 +v -0.000807 0.386972 0.100000 +v 0.131850 0.386972 0.100000 +v 0.456404 -0.177362 0.100000 +v 0.326590 -0.177362 0.100000 +v -0.456377 -0.177362 -0.100000 +v -0.130392 0.386972 -0.100000 +v 0.195574 -0.177362 -0.100000 +v 0.064101 -0.177362 -0.100000 +v 0.000605 -0.064495 -0.100000 +v -0.000578 -0.064495 -0.100000 +v -0.130392 0.161238 -0.100000 +v -0.260225 -0.064495 -0.100000 +v -0.130640 -0.064495 -0.100000 +v -0.065714 -0.177362 -0.100000 +v -0.000807 0.386972 -0.100000 +v 0.131850 0.386972 -0.100000 +v 0.456404 -0.177362 -0.100000 +v 0.326590 -0.177362 -0.100000 +v -0.456377 -0.177362 -0.100000 +v -0.456377 -0.177362 0.100000 +v -0.130392 0.386972 -0.100000 +v -0.130392 0.386972 0.100000 +v 0.195574 -0.177362 -0.100000 +v 0.195574 -0.177362 0.100000 +v 0.064101 -0.177362 -0.100000 +v 0.064101 -0.177362 0.100000 +v 0.000605 -0.064495 -0.100000 +v 0.000605 -0.064495 0.100000 +v -0.000578 -0.064495 -0.100000 +v -0.000578 -0.064495 0.100000 +v -0.130392 0.161238 -0.100000 +v -0.130392 0.161238 0.100000 +v -0.260225 -0.064495 -0.100000 +v -0.260225 -0.064495 0.100000 +v -0.130640 -0.064495 -0.100000 +v -0.130640 -0.064495 0.100000 +v -0.065714 -0.177362 -0.100000 +v -0.065714 -0.177362 0.100000 +v -0.000807 0.386972 -0.100000 +v -0.000807 0.386972 0.100000 +v 0.131850 0.386972 -0.100000 +v 0.131850 0.386972 0.100000 +v 0.456404 -0.177362 -0.100000 +v 0.456404 -0.177362 0.100000 +v 0.326590 -0.177362 -0.100000 +v 0.326590 -0.177362 0.100000 +vt 0.421665 0.073017 +vt 0.522635 0.102115 +vt 0.547876 0.145815 +vt 0.547876 0.000116 +vt 0.447360 0.438080 +vt 0.421665 0.306888 +vt 0.447360 0.291982 +vt 0.422215 0.452668 +vt 0.472259 0.072934 +vt 0.521736 0.044355 +vt 0.522753 0.043768 +vt 0.497345 0.058444 +vt 0.497293 0.029334 +vt 0.522424 0.014818 +vt 0.522632 0.248050 +vt 0.547874 0.146048 +vt 0.547874 0.291749 +vt 0.421665 0.218952 +vt 0.447592 0.437761 +vt 0.473287 0.306569 +vt 0.473287 0.452668 +vt 0.448142 0.291982 +vt 0.522523 0.189832 +vt 0.472030 0.218999 +vt 0.472658 0.218189 +vt 0.497437 0.204323 +vt 0.472247 0.189734 +vt 0.447116 0.204250 +vt 0.076240 0.346088 +vt 0.026836 0.999884 +vt 0.000116 0.401267 +vt 0.195839 0.239879 +vt 0.119146 0.886700 +vt 0.199779 0.122423 +vt 0.218708 0.854732 +vt 0.218708 0.000116 +vt 0.190671 0.867320 +vt 0.144083 0.110395 +vt 0.163261 0.874117 +vt 0.154549 0.213758 +vt 0.121602 0.878203 +vt 0.142716 0.293683 +vt 0.108497 0.899797 +vt 0.077107 0.348728 +vt 0.072698 0.945142 +vt 0.005764 0.389783 +vt 0.038953 0.954045 +vt 0.011123 0.962618 +vt 0.410801 0.093210 +vt 0.372929 0.429435 +vt 0.421432 0.095882 +vt 0.218941 0.332922 +vt 0.421432 0.428236 +vt 0.228947 0.335425 +vt 0.268290 0.000116 +vt 0.221375 0.000820 +vn -0.0000 0.0000 1.0000 +vn 0.0000 -0.0000 -1.0000 +vn 0.0000 1.0000 0.0000 +vn -0.8661 -0.4999 0.0000 +vn 0.8661 -0.4999 0.0000 +vn -0.5048 -0.8632 0.0000 +vn -0.5007 -0.8656 0.0000 +vn -0.0000 -1.0000 0.0000 +vn 0.8656 0.5007 0.0000 +vn 0.5007 0.8656 0.0000 +vn 0.8656 -0.5007 0.0000 +vn -0.8665 0.4991 0.0000 +vn -0.4991 -0.8665 0.0000 +usemtl SVGMat.002 +s off +f 1/1/1 7/2/1 2/3/1 +f 7/2/1 3/4/1 2/3/1 +f 14/5/1 12/6/1 11/7/1 +f 14/5/1 13/8/1 12/6/1 +f 1/1/1 8/9/1 7/2/1 +f 6/10/1 5/11/1 7/2/1 +f 5/11/1 3/4/1 7/2/1 +f 1/1/1 9/12/1 8/9/1 +f 1/1/1 10/13/1 9/12/1 +f 4/14/1 3/4/1 5/11/1 +f 21/15/2 15/16/2 16/17/2 +f 17/18/2 21/15/2 16/17/2 +f 26/19/2 28/20/2 25/21/2 +f 27/22/2 28/20/2 26/19/2 +f 22/23/2 15/16/2 21/15/2 +f 19/24/2 20/25/2 21/15/2 +f 17/18/2 19/24/2 21/15/2 +f 23/26/2 15/16/2 22/23/2 +f 24/27/2 15/16/2 23/26/2 +f 17/18/2 18/28/2 19/24/2 +s 1 +f 32/29/3 29/30/4 30/31/4 +f 34/32/5 31/33/3 32/29/3 +f 36/34/6 33/35/5 34/32/5 +f 38/36/6 35/37/6 36/34/6 +f 40/38/7 37/39/6 38/36/6 +f 42/40/8 39/41/7 40/38/7 +f 44/42/9 41/43/8 42/40/8 +f 46/44/10 43/45/9 44/42/9 +f 48/46/11 45/47/10 46/44/10 +f 30/31/4 47/48/11 48/46/11 +f 52/49/10 49/50/12 50/51/12 +f 54/52/11 51/53/10 52/49/10 +f 56/54/13 53/55/11 54/52/11 +f 50/51/12 55/56/13 56/54/13 +f 32/29/3 31/33/3 29/30/4 +f 34/32/5 33/35/5 31/33/3 +f 36/34/6 35/37/6 33/35/5 +f 38/36/6 37/39/6 35/37/6 +f 40/38/7 39/41/7 37/39/6 +f 42/40/8 41/43/8 39/41/7 +f 44/42/9 43/45/9 41/43/8 +f 46/44/10 45/47/10 43/45/9 +f 48/46/11 47/48/11 45/47/10 +f 30/31/4 29/30/4 47/48/11 +f 52/49/10 51/53/10 49/50/12 +f 54/52/11 53/55/11 51/53/10 +f 56/54/13 55/56/13 53/55/11 +f 50/51/12 49/50/12 55/56/13 diff --git a/matrix_display/display b/matrix_display/display new file mode 100755 index 0000000..37ece7a Binary files /dev/null and b/matrix_display/display differ diff --git a/matrix_display/proj b/matrix_display/proj new file mode 100644 index 0000000..cc7235d --- /dev/null +++ b/matrix_display/proj @@ -0,0 +1,4 @@ +3.7808547, 0, 0, 0 +0, 5.671282, 0, 0 +0, 0, -1.0833334, -4.1666665 +0, 0, -1, 0 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..959203b --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 80 +hard_tabs = false diff --git a/src/complex.rs b/src/complex.rs new file mode 100644 index 0000000..025cbbb --- /dev/null +++ b/src/complex.rs @@ -0,0 +1,200 @@ +use std::{ + iter::Sum, + ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign, + }, +}; + +use crate::scalar::Scalar; + +#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Debug)] +pub struct Complex { + x: f32, + y: f32, +} + +impl From<[f32; 2]> for Complex { + fn from(value: [f32; 2]) -> Self { + Complex { + x: value[0], + y: value[1], + } + } +} + +impl Scalar for Complex { + type AbsOutput = f32; + + fn abs(self) -> Self::AbsOutput { + f32::sqrt(self.x.powi(2) + self.y.powi(2)) + } + + fn inv(self) -> Self { + Complex { + x: 1. / self.x, + y: 1. / self.y, + } + } + + fn sqrt(v: Self) -> Self { + Complex { + x: f32::sqrt(v.x), + y: f32::sqrt(v.y), + } + } + + fn one() -> Self { + Complex::from([1., 0.]) + } + + fn mul_add(self, a: Self, b: Self) -> Self { + Complex { + x: f32::mul_add(self.x, a.x, b.x), + y: f32::mul_add(self.y, a.y, b.y), + } + } + + type TanOutput = Complex; + fn tan(self) -> Self::TanOutput { + Complex { + x: f32::sin(2. * self.x) + / (f32::cos(2. * self.x) + f32::cosh(2. * self.y)), + y: f32::sinh(2. * self.y) + / (f32::cos(2. * self.x) + f32::cosh(2. * self.y)), + } + } +} + +impl Sum for Complex { + fn sum>(iter: I) -> Self { + iter.fold(Complex::default(), |a, b| Complex { + x: a.x + b.x, + y: a.y + b.y, + }) + } +} + +impl Neg for Complex { + type Output = Self; + fn neg(self) -> Self::Output { + Complex { + x: self.x.neg(), + y: self.y.neg(), + } + } +} + +impl Add for Complex { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Complex { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl AddAssign for Complex { + fn add_assign(&mut self, rhs: Self) { + self.x += rhs.x; + self.y += rhs.y; + } +} + +impl AddAssign<&Complex> for Complex { + fn add_assign(&mut self, rhs: &Complex) { + self.x += rhs.x; + self.y += rhs.y; + } +} + +impl Sub for Complex { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + Complex { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl SubAssign for Complex { + fn sub_assign(&mut self, rhs: Self) { + self.x -= rhs.x; + self.y -= rhs.y; + } +} + +impl SubAssign<&Complex> for Complex { + fn sub_assign(&mut self, rhs: &Complex) { + self.x -= rhs.x; + self.y -= rhs.y; + } +} + +impl Mul for Complex { + type Output = Self; + fn mul(self, rhs: Self) -> Self::Output { + Complex { + x: self.x * rhs.x - self.y * rhs.y, + y: self.x * rhs.y + self.y * rhs.x, + } + } +} + +impl MulAssign for Complex { + fn mul_assign(&mut self, rhs: Self) { + let x = self.x * rhs.x - self.y * rhs.y; + self.y = self.x * rhs.y + self.y * rhs.x; + self.x = x; + } +} + +impl MulAssign<&Complex> for Complex { + fn mul_assign(&mut self, rhs: &Complex) { + let x = self.x * rhs.x - self.y * rhs.y; + self.y = self.x * rhs.y + self.y * rhs.x; + self.x = x; + } +} + +impl Mul for Complex { + type Output = Complex; + fn mul(self, rhs: f32) -> Self::Output { + Complex { + x: self.x * rhs, + y: self.y * rhs, + } + } +} + +impl MulAssign for Complex { + fn mul_assign(&mut self, rhs: f32) { + self.x *= rhs; + self.y *= rhs; + } +} + +impl Div for Complex { + type Output = Self; + fn div(self, rhs: Self) -> Self { + Complex { + x: self.x / rhs.x, + y: self.y / rhs.y, + } + } +} + +impl DivAssign<&Complex> for Complex { + fn div_assign(&mut self, rhs: &Complex) { + self.x /= rhs.x; + self.y /= rhs.y; + } +} + +impl DivAssign for Complex { + fn div_assign(&mut self, rhs: Self) { + self.x /= rhs.x; + self.y /= rhs.y; + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..08d9d08 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,31 @@ +pub mod complex; + +pub mod scalar; + +pub mod matrix; +pub mod vector; + +use std::ops::{Add, Mul, Sub}; + +pub use matrix::Matrix; +pub use vector::ScaledVector; +pub use vector::Vector; + +pub use matrix::projection; +pub use vector::angle_cos; +pub use vector::cross_product; +pub use vector::linear_combination; + +pub fn lerp< + V: Clone + Sub + Mul + Add, +>( + u: V, + v: V, + t: f32, +) -> V { + match t { + 0. => u.clone(), + 1. => v.clone(), + p => u.clone() + (v - u) * p, + } +} diff --git a/src/matrix.rs b/src/matrix.rs new file mode 100644 index 0000000..0204bf6 --- /dev/null +++ b/src/matrix.rs @@ -0,0 +1,433 @@ +use std::{ + fmt::Debug, + ops::{Add, AddAssign, Index, IndexMut, Mul, MulAssign, Sub, SubAssign}, + slice::{Chunks, ChunksMut}, +}; + +use crate::{scalar::Scalar, vector::Vector}; + +#[derive(Clone)] +pub struct Matrix { + pub data: Vec, + pub rows: usize, + pub cols: usize, +} + +impl Debug for Matrix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("[")?; + for i in 0..self.rows { + write!(f, "{:?}", &self[i])?; + if i != self.rows - 1 { + writeln!(f, ",")?; + } + } + f.write_str("]")?; + Ok(()) + } +} + +impl From<[[K; D]; N]> for Matrix { + fn from(value: [[K; D]; N]) -> Self { + let mut vec = Vec::with_capacity(N * D); + (0..N).for_each(|i| { + for j in 0..D { + vec.push(value[i][j].clone()); + } + }); + + Matrix { + rows: N, + cols: D, + data: vec, + } + } +} + +impl Index for Matrix { + type Output = [K]; + + fn index(&self, index: usize) -> &[K] { + let start = index * self.cols; + &self.data[start..start + self.cols] + } +} + +impl IndexMut for Matrix { + fn index_mut(&mut self, index: usize) -> &mut [K] { + let start = index * self.cols; + &mut self.data[start..start + self.cols] + } +} + +impl Add for Matrix { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + assert_eq!( + self.shape(), + other.shape(), + "matrices must be the same size" + ); + + let mut vec = Vec::with_capacity(self.rows * self.cols); + for i in 0..self.data.len() { + vec.push(self.data[i] + other.data[i]); + } + + Matrix { + data: vec, + cols: self.cols, + rows: self.rows, + } + } +} + +impl AddAssign<&Matrix> for Matrix { + fn add_assign(&mut self, rhs: &Matrix) { + assert_eq!(self.shape(), rhs.shape(), "matrices must be the same size"); + + for i in 0..self.data.len() { + self.data[i] += rhs.data[i] + } + } +} + +impl Sub for Matrix { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + assert_eq!( + self.shape(), + other.shape(), + "matrices must be the same size" + ); + + let mut vec = Vec::with_capacity(self.rows * self.cols); + for i in 0..self.data.len() { + vec.push(self.data[i] - other.data[i]); + } + + Matrix { + data: vec, + cols: self.cols, + rows: self.rows, + } + } +} + +impl SubAssign<&Matrix> for Matrix { + fn sub_assign(&mut self, rhs: &Matrix) { + assert_eq!(self.shape(), rhs.shape(), "matrices must be the same size"); + + for i in 0..self.data.len() { + self.data[i] -= rhs.data[i]; + } + } +} + +impl Mul for Matrix { + type Output = Matrix; + + fn mul(self, a: K) -> Self::Output { + let mut vec = Vec::with_capacity(self.data.len()); + for i in 0..self.data.len() { + vec.push(self.data[i] * a); + } + + Matrix { + data: vec, + cols: self.cols, + rows: self.rows, + } + } +} + +impl MulAssign<&K> for Matrix { + fn mul_assign(&mut self, rhs: &K) { + for i in 0..self.data.len() { + self.data[i] *= rhs; + } + } +} + +impl Mul<&Vector> for &Matrix { + type Output = Vector; + + fn mul(self, rhs: &Vector) -> Self::Output { + assert_eq!( + self.shape().0, + rhs.size(), + "bad input for matrix and vector column multiplication" + ); + + let mut vec = Vec::with_capacity(rhs.size()); + for i in 0..rhs.size() { + vec.push(&self[i] * rhs); + } + Vector::from(vec) + } +} + +impl Mul<&Matrix> for &Matrix { + type Output = Matrix; + + fn mul(self, rhs: &Matrix) -> Self::Output { + let cols = rhs.cols; + let rows = self.rows; + let mut vec = Vec::with_capacity(rows * cols); + + for i in 0..rows { + for j in 0..cols { + let mut val = self[i][0] * rhs[0][j]; + for r in 1..self.cols { + val += self[i][r] * rhs[r][j]; + } + vec.push(val); + } + } + + Matrix { + data: vec, + rows, + cols, + } + } +} + +impl Matrix { + pub fn shape(&self) -> (usize, usize) { + (self.rows, self.cols) + } + pub fn is_square(&self) -> bool { + self.rows == self.cols + } + pub fn iter_mut(&mut self) -> ChunksMut<'_, K> { + self.data.chunks_mut(self.cols) + } + + pub fn iter(&self) -> Chunks<'_, K> { + self.data.chunks(self.cols) + } + + pub fn add(&mut self, v: &Matrix) { + *self += v; + } + + pub fn sub(&mut self, v: &Matrix) { + *self -= v; + } + + pub fn scl(&mut self, a: K) { + *self *= &a; + } + + pub fn mul_vec(&self, vec: &Vector) -> Vector { + self * vec + } + + pub fn mul_mat(&self, mat: &Matrix) -> Matrix { + self * mat + } + + pub fn trace(&self) -> Option { + assert!(self.is_square(), "matrix must be squared"); + + match self.rows { + 0 => None, + _ => Some((0..self.rows).map(|i| self[i][i]).sum()), + } + } + + pub fn transpose(&self) -> Matrix { + let mut vec = Vec::with_capacity(self.rows * self.cols); + for i in 0..self.cols { + for j in 0..self.rows { + vec.push(self[j][i]); + } + } + + Matrix { + rows: self.cols, + cols: self.rows, + data: vec, + } + } + + pub fn row_echelon(&self) -> Matrix { + let mut ret = self.clone(); + ret.row_echelon_mut(); + ret + } + + pub fn row_echelon_mut(&mut self) -> &mut Self { + let mut col: usize = 0; + + for r in 0..self.rows { + let mut row = None; + + for j in col..self.cols { + for i in r..self.rows { + if self[i][j] != K::default() { + row = Some((i, j)); + break; + } + } + + if row.is_some() { + break; + } + } + + if row.is_none() { + break; + } + let cur = row.unwrap(); + col = cur.1; + if r != cur.0 { + for c in col..self.cols { + let tmp = self[r][c]; + self[r][c] = self[cur.0][c]; + self[cur.0][c] = tmp; + } + } + + let p = self[r][col]; + for i in 0..self.rows { + if i == r { + continue; + } + let cur = self[i][col]; + if cur == K::default() { + continue; + } + + let a = -cur / p; + for j in col..self.cols { + let x = self[r][j]; + self[i][j] += x * a; + } + } + + for i in col..self.cols { + self[r][i] *= p.inv(); + } + } + + self + } + + pub fn determinant(&self) -> K { + assert!(self.is_square(), "matrix must be squared"); + + fn det( + mat: &[K], + row_size: usize, + cur: usize, + skip_cols: &[bool], + ) -> K { + let mut cols = Vec::with_capacity(skip_cols.len()); + + for (col, skip) in skip_cols.iter().enumerate() { + if !*skip { + cols.push(col); + } + } + + match cur { + 0 => K::default(), + 1 => mat[0], + 2 => { + mat[cols[0]] * mat[row_size + cols[1]] + - mat[cols[1]] * mat[row_size + cols[0]] + } + _ => { + let mut result = K::default(); + for (i, col) in cols.iter().enumerate() { + let mut m_col = skip_cols.to_vec(); + m_col[*col] = true; + let ret = mat[*col] + * det(&mat[row_size..], row_size, cur - 1, &m_col); + result += if i % 2 == 1 { -ret } else { ret }; + m_col[*col] = false; + } + result + } + } + } + + det( + self.data.as_slice(), + self.rows, + self.rows, + &vec![false; self.cols], + ) + } + + pub fn inverse(&self) -> Result, &'static str> { + let mut aug_m = Matrix { + data: vec![K::default(); self.rows * self.cols * 2], + cols: self.cols * 2, + rows: self.rows, + }; + + for row in 0..self.rows { + for col in 0..self.cols { + aug_m.data[row * (self.cols * 2) + col] = self[row][col]; + } + aug_m.data[row * (self.cols * 2) + self.cols + row] = K::one(); + } + + aug_m.row_echelon_mut(); + + let mut vec = Vec::with_capacity(self.rows * self.cols); + + for row in aug_m.data.chunks(self.cols * 2) { + for &v in row.iter().skip(self.cols) { + vec.push(v); + } + } + + Ok(Matrix { + data: vec, + cols: self.cols, + rows: self.rows, + }) + } + + pub fn rank(&self) -> usize { + let mat = self.row_echelon(); + let mut rank = 0; + + let mut cur = 0; + for row in mat.iter() { + for (j, v) in row.iter().enumerate().skip(cur) { + if *v != K::default() { + cur = j + 1; + rank += 1; + break; + } + } + } + + rank + } +} + +pub fn projection(fov: f32, ratio: f32, near: f32, far: f32) -> Matrix { + let tan = f32::tan(fov.to_radians() / 2.); + let top = near * tan; + let right = top * ratio; + + Matrix::from([ + [near / right, 0.0, 0.0, 0.0], + [0.0, near / top, 0.0, 0.0], + [ + 0.0, + 0.0, + -(far + near) / (far - near), + -2. * far * near / (far - near), + ], + [0.0, 0.0, -1.0, 0.0], + ]) +} diff --git a/src/scalar.rs b/src/scalar.rs new file mode 100644 index 0000000..549efc2 --- /dev/null +++ b/src/scalar.rs @@ -0,0 +1,90 @@ +use std::{fmt::Debug, iter::Sum}; + +pub trait Scalar: + Debug + + Copy + + Default + + std::cmp::PartialOrd + + std::ops::Neg + + std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Div + + for<'a> std::ops::AddAssign<&'a Self> + + std::ops::AddAssign + + for<'a> std::ops::SubAssign<&'a Self> + + std::ops::SubAssign + + for<'a> std::ops::MulAssign<&'a Self> + + std::ops::MulAssign + + for<'a> std::ops::DivAssign<&'a Self> + + std::ops::DivAssign + + std::iter::Sum +{ + type AbsOutput: Sum + Default + Copy + std::cmp::PartialOrd; + type TanOutput; + + fn abs(self) -> Self::AbsOutput; + fn mul_add(self, a: Self, b: Self) -> Self; + fn sqrt(v: Self) -> Self; + fn one() -> Self; + fn inv(self) -> Self; + fn tan(self) -> Self::TanOutput; +} + +impl Scalar for f32 { + type AbsOutput = f32; + + fn abs(self) -> Self::AbsOutput { + f32::abs(self) + } + + fn sqrt(v: Self) -> Self { + v.sqrt() + } + + fn one() -> Self { + 1. + } + + fn mul_add(self, a: Self, b: Self) -> Self { + self.mul_add(a, b) + } + + fn inv(self) -> Self { + 1. / self + } + + type TanOutput = f32; + fn tan(self) -> Self::TanOutput { + f32::tan(self) + } +} + +impl Scalar for f64 { + type AbsOutput = f64; + + fn abs(self) -> Self::AbsOutput { + f64::abs(self) + } + + fn sqrt(v: Self) -> Self { + v.sqrt() + } + + fn one() -> Self { + 1. + } + + fn mul_add(self, a: Self, b: Self) -> Self { + self.mul_add(a, b) + } + + fn inv(self) -> Self { + 1. / self + } + + type TanOutput = f64; + fn tan(self) -> Self::TanOutput { + f64::tan(self) + } +} diff --git a/src/vector.rs b/src/vector.rs new file mode 100644 index 0000000..624a181 --- /dev/null +++ b/src/vector.rs @@ -0,0 +1,278 @@ +use std::{ + fmt::Debug, + iter::Sum, + ops::{Add, AddAssign, Index, Mul, MulAssign, Sub, SubAssign}, + slice::{Iter, IterMut}, +}; + +use crate::scalar::Scalar; + +#[derive(Clone, Default)] +pub struct Vector { + pub data: Vec, +} + +#[derive(Clone, Debug)] +pub struct ScaledVector<'a, K: Scalar> { + pub v: &'a Vector, + pub k: &'a K, +} + +impl Debug for Vector { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl From<[K; N]> for Vector { + fn from(data: [K; N]) -> Self { + Vector { + data: Vec::from(data), + } + } +} + +impl From> for Vector { + fn from(data: Vec) -> Self { + Vector { data } + } +} + +impl Index for Vector { + type Output = K; + + fn index(&self, index: usize) -> &Self::Output { + &self.data[index] + } +} + +impl Add for Vector { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + assert_eq!(self.size(), other.size(), "vectors must be the same size"); + + let mut vec = Vec::with_capacity(self.size()); + for i in 0..self.size() { + vec.push(self[i] + other[i]); + } + + Vector::from(vec) + } +} + +impl AddAssign<&Vector> for Vector { + fn add_assign(&mut self, rhs: &Vector) { + assert_eq!(self.size(), rhs.size(), "vectors must be the same size"); + + for i in 0..self.size() { + self.data[i] += rhs.data[i]; + } + } +} + +impl<'a, K: Scalar> AddAssign> for Vector { + fn add_assign(&mut self, rhs: ScaledVector<'a, K>) { + assert_eq!(self.size(), rhs.v.size(), "vectors must be the same size"); + + for i in 0..self.size() { + self.data[i] = rhs.v.data[i].mul_add(*rhs.k, self.data[i]); + } + } +} + +impl Sub for Vector { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + assert_eq!(self.size(), rhs.size(), "vectors must be the same size"); + + let mut vec = Vec::with_capacity(self.size()); + for i in 0..self.size() { + vec.push(self.data[i] - rhs.data[i]); + } + + Vector::from(vec) + } +} + +impl SubAssign<&Vector> for Vector { + fn sub_assign(&mut self, rhs: &Vector) { + assert_eq!(self.size(), rhs.size(), "vectors must be the same size"); + + self.iter_mut() + .zip(rhs.iter()) + .for_each(|(x1, x2)| x1.sub_assign(x2)); + } +} + +impl Mul for Vector { + type Output = Self; + + fn mul(self, a: K) -> Self::Output { + &self * &a + } +} + +impl Mul<&K> for &Vector { + type Output = Vector; + + fn mul(self, &a: &K) -> Self::Output { + let mut vec = Vec::with_capacity(self.size()); + + for i in 0..self.size() { + vec.push(self.data[i] * a); + } + Vector::from(vec) + } +} + +impl MulAssign<&K> for Vector { + fn mul_assign(&mut self, a: &K) { + for i in self.iter_mut() { + *i *= a; + } + } +} + +impl Mul<&Vector> for &Vector { + type Output = K; + + fn mul(self, other: &Vector) -> Self::Output { + self.iter() + .zip(other.iter()) + .map(|(&x1, &x2)| x1.mul(x2)) + .sum() + } +} + +impl Mul<&Vector> for &[K] { + type Output = K; + + fn mul(self, other: &Vector) -> Self::Output { + self.iter() + .zip(other.iter()) + .map(|(&x1, &x2)| x1.mul(x2)) + .sum() + } +} + +impl<'a, K: Scalar> Sum> for Vector { + fn sum>>(mut iter: I) -> Self { + match iter.next().map(|vec| vec.v * vec.k) { + None => Vector::default(), + Some(first) => iter.fold(first, |mut acc, cur| { + acc += cur; + acc + }), + } + } +} + +#[macro_export] +macro_rules! linear_combination { + ($u:expr, $coefs:expr) => { + $u.into_iter() + .zip($coefs) + .map(|(v, k)| $crate::ScaledVector { v, k }) + .sum() + }; +} + +pub fn linear_combination( + u: &[&Vector], + coefs: &[K], +) -> Vector { + assert_eq!( + u.len(), + coefs.len(), + "vectors and scalers must be the same size" + ); + assert!(!u.is_empty(), "list must not be empty"); + + assert!( + u.iter().all(|v| v.size() == u[0].size()), + "vectors must be have the same dimention" + ); + + linear_combination!(u.iter().copied(), coefs) +} + +pub fn angle_cos(u: &Vector, v: &Vector) -> K { + assert_eq!(u.size(), v.size(), "vectors must be the same size"); + assert!(!u.is_empty(), "vectors must be the same size"); + + u.dot(v) / (u.norm() * v.norm()) +} + +pub fn cross_product(u: &Vector, v: &Vector) -> Vector { + assert!( + u.size() == 3 && v.size() == u.size(), + "vectors must have be of size 3" + ); + + Vector::from([ + (u[1] * v[2]) - (u[2] * v[1]), + (u[2] * v[0]) - (u[0] * v[2]), + (u[0] * v[1]) - (u[1] * v[0]), + ]) +} + +impl Vector { + pub fn zero(size: usize) -> Self { + Vector::from(vec![K::default(); size]) + } + + pub fn size(&self) -> usize { + self.data.len() + } + + pub fn iter(&self) -> Iter<'_, K> { + self.data.iter() + } + + pub fn iter_mut(&mut self) -> IterMut<'_, K> { + self.data.iter_mut() + } + + pub fn first(&self) -> Option<&K> { + self.data.first() + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn add(&mut self, v: &Vector) { + *self += v; + } + + pub fn sub(&mut self, vec: &Vector) { + *self -= vec; + } + + pub fn scl(&mut self, a: K) { + *self *= &a; + } + + pub fn dot(&self, v: &Vector) -> K { + assert_eq!(v.size(), self.size(), "vectors must be the same size"); + + self * v + } + + pub fn norm_1(&self) -> K::AbsOutput { + self.iter().map(|&x| x.abs()).sum::() + } + + pub fn norm(&self) -> K { + K::sqrt(self.iter().map(|&x| x * x).sum()) + } + + pub fn norm_inf(&self) -> K::AbsOutput { + self.iter().map(|&x| x.abs()).fold( + K::AbsOutput::default(), + |acc, cur| if acc > cur { acc } else { cur }, + ) + } +} diff --git a/tests/add.rs b/tests/add.rs new file mode 100644 index 0000000..dcbcefc --- /dev/null +++ b/tests/add.rs @@ -0,0 +1,72 @@ +use matrix::{complex::Complex, Matrix, Vector}; + +#[test] +fn test_add() { + let mut u: Vector = Vector::from([2., 3.]); + let v = Vector::from([5., 7.]); + u.add(&v); + assert_eq!(u[0], 7.); + assert_eq!(u[1], 10.); + + let mut u = Matrix::from([[1., 2.], [3., 4.]]); + let v = Matrix::from([[7., 4.], [-2., 2.]]); + u.add(&v); + assert_eq!(u[0][0], 8.); + assert_eq!(u[0][1], 6.); + assert_eq!(u[1][0], 1.); + assert_eq!(u[1][1], 6.); + + for i in 0..20 { + let size = i; + let mut v = vec![]; + for i in 0..size { + v.push(i as f32); + } + let mut a: Vector = Vector::from(v); + let b = a.clone(); + a.add(&b); + + for i in 0..size { + assert_eq!(a[i] / 2., b[i], "a[i] and b[i] must equal") + } + } +} + +#[test] +fn test_add_complex() { + let mut u = + Vector::from([Complex::from([1., 2.]), Complex::from([-1., -3.])]); + let v = Vector::from([Complex::from([1., 3.]), Complex::from([2., 3.])]); + u.add(&v); + assert_eq!(u[0], Complex::from([2., 5.])); + assert_eq!(u[1], Complex::from([1., 0.])); + + let mut u = Matrix::from([ + [Complex::from([1., 0.]), Complex::from([2., 0.])], + [Complex::from([3., 0.]), Complex::from([5., 0.])], + ]); + let v = Matrix::from([ + [Complex::from([7., 0.]), Complex::from([3., 0.])], + [Complex::from([-2., 0.]), Complex::from([2., 0.])], + ]); + u.add(&v); + // assert_eq!(u[0][0], 8.); + // assert_eq!(u[0][1], 6.); + // assert_eq!(u[1][0], 1.); + // assert_eq!(u[1][1], 6.); + + for i in 0..20 { + let size = i; + let mut v = vec![]; + for i in 0..size { + v.push(i as f32); + } + let mut a: Vector = Vector::from(v); + let b = a.clone(); + a.add(&b); + + for i in 0..size { + assert_eq!(a[i] / 2., b[i], "a[i] and b[i] must equal") + } + } +} diff --git a/tests/angle_cos.rs b/tests/angle_cos.rs new file mode 100644 index 0000000..fdda63c --- /dev/null +++ b/tests/angle_cos.rs @@ -0,0 +1,30 @@ +use matrix::{angle_cos, Vector}; + +macro_rules! approx_eq { + ($a: expr, $b: expr) => { + ($a - $b).abs() < 1e-6 + }; +} + +#[test] +fn test_angle_cos() { + let u = Vector::from([1., 0.]); + let v = Vector::from([1., 0.]); + assert_eq!(angle_cos(&u, &v), 1.); + + let u = Vector::from([1., 0.]); + let v = Vector::from([0., 1.]); + assert_eq!(angle_cos(&u, &v), 0.); // 0.0 + + let u = Vector::from([-1., 1.]); + let v = Vector::from([1., -1.]); + assert!(approx_eq!(angle_cos(&u, &v), -1.0_f64)); // -1.0 + + let u = Vector::from([2., 1.]); + let v = Vector::from([4., 2.]); + assert!(approx_eq!(angle_cos(&u, &v), 1.0f64)); // 1.0 + + let u = Vector::from([1., 2., 3.]); + let v = Vector::from([4., 5., 6.]); + assert!(approx_eq!(angle_cos(&u, &v), 0.974631846_f64)); +} diff --git a/tests/cross_product.rs b/tests/cross_product.rs new file mode 100644 index 0000000..0dc0342 --- /dev/null +++ b/tests/cross_product.rs @@ -0,0 +1,25 @@ +use matrix::{cross_product, Vector}; + +#[test] +fn test_cross_product() { + let u = Vector::from([0., 0., 1.]); + let v = Vector::from([1., 0., 0.]); + let a = cross_product(&u, &v); + assert_eq!(a[0], 0.); + assert_eq!(a[1], 1.); + assert_eq!(a[2], 0.); + + let u = Vector::from([1., 2., 3.]); + let v = Vector::from([4., 5., 6.]); + let b = cross_product(&u, &v); + assert_eq!(b[0], -3.); + assert_eq!(b[1], 6.); + assert_eq!(b[2], -3.); + + let u = Vector::from([4., 2., -3.]); + let v = Vector::from([-2., -5., 16.]); + let c = cross_product(&u, &v); + assert_eq!(c[0], 17.); + assert_eq!(c[1], -58.); + assert_eq!(c[2], -16.); +} diff --git a/tests/determinant.rs b/tests/determinant.rs new file mode 100644 index 0000000..8a091f2 --- /dev/null +++ b/tests/determinant.rs @@ -0,0 +1,25 @@ +use matrix::Matrix; + +#[test] +fn test_determinant() { + let u = Matrix::from([[1., -1.], [-1., 1.]]); + let a = u.determinant(); // 0.0 + assert_eq!(a, 0.); + + let u = Matrix::from([[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]); + let a = u.determinant(); // 8.0 + assert_eq!(a, 8.); + + let u = Matrix::from([[8., 5., -2.], [4., 7., 20.], [7., 6., 1.]]); + let a = u.determinant(); // -174.0 + assert_eq!(a, -174.); + + let u = Matrix::from([ + [8., 5., -2., 4.], + [4., 2.5, 20., 4.], + [8., 5., 1., 4.], + [28., -4., 17., 1.], + ]); + let a = u.determinant(); // 1032 + assert_eq!(a, 1032.); +} diff --git a/tests/dot.rs b/tests/dot.rs new file mode 100644 index 0000000..7fba9c5 --- /dev/null +++ b/tests/dot.rs @@ -0,0 +1,16 @@ +use matrix::Vector; + +#[test] +fn test_dot() { + let u = Vector::from([0., 0.]); + let v = Vector::from([1., 1.]); + assert_eq!(u.dot(&v), 0.); + + let u = Vector::from([1., 1.]); + let v = Vector::from([1., 1.]); + assert_eq!(u.dot(&v), 2.); + + let u = Vector::from([-1., 6.]); + let v = Vector::from([3., 2.]); + assert_eq!(u.dot(&v), 9.); +} diff --git a/tests/inverse.rs b/tests/inverse.rs new file mode 100644 index 0000000..9928200 --- /dev/null +++ b/tests/inverse.rs @@ -0,0 +1,47 @@ +use matrix::Matrix; + +macro_rules! approx_eq { + ($a: expr, $b: expr) => { + ($a - $b).abs() < 1e-6 + }; +} + +#[test] +fn test_inverse() { + let u = Matrix::from([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]); + let a = u.inverse().unwrap(); + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 0.); + assert_eq!(a[0][2], 0.); + assert_eq!(a[1][0], 0.); + assert_eq!(a[1][1], 1.); + assert_eq!(a[1][2], 0.); + assert_eq!(a[2][0], 0.); + assert_eq!(a[2][1], 0.); + assert_eq!(a[2][2], 1.); + + let u = Matrix::from([[2., 0., 0.], [0., 2., 0.], [0., 0., 2.]]); + let a = u.inverse().unwrap(); + + assert_eq!(a[0][0], 0.5); + assert_eq!(a[0][1], 0.); + assert_eq!(a[0][2], 0.); + assert_eq!(a[1][0], 0.); + assert_eq!(a[1][1], 0.5); + assert_eq!(a[1][2], 0.); + assert_eq!(a[2][0], 0.); + assert_eq!(a[2][1], 0.); + assert_eq!(a[2][2], 0.5); + + let u = Matrix::from([[8., 5., -2.], [4., 7., 20.], [7., 6., 1.]]); + let a = u.inverse().unwrap(); + assert!(approx_eq!(a[0][0], 0.649425287_f64)); + assert!(approx_eq!(a[0][1], 0.097701149_f64)); + assert!(approx_eq!(a[0][2], -0.655172414_f64)); + assert!(approx_eq!(a[1][0], -0.781609195_f64)); + assert!(approx_eq!(a[1][1], -0.126436782_f64)); + assert!(approx_eq!(a[1][2], 0.965517241_f64)); + assert!(approx_eq!(a[2][0], 0.143678161_f64)); + assert!(approx_eq!(a[2][1], 0.074712644_f64)); + assert!(approx_eq!(a[2][2], -0.206896552_f64)); +} diff --git a/tests/lerp.rs b/tests/lerp.rs new file mode 100644 index 0000000..234e2d6 --- /dev/null +++ b/tests/lerp.rs @@ -0,0 +1,25 @@ +use matrix::{lerp, Matrix, Vector}; + +#[test] +fn test_lerp() { + assert_eq!(lerp(0., 1., 0.), 0.); // 0.0 + assert_eq!(lerp(0., 1., 1.), 1.); // 1.0 + assert_eq!(lerp(0., 1., 0.5), 0.5); // 0.5 + assert_eq!(lerp(21., 42., 0.3), 27.3); // 27.3 + + let a = lerp(Vector::from([2., 1.]), Vector::from([4., 2.]), 0.3); + + assert_eq!(a[0], 2.6); + assert_eq!(a[1], 1.3); + + let b = lerp( + Matrix::from([[2., 1.], [3., 4.]]), + Matrix::from([[20., 10.], [30., 40.]]), + 0.5, + ); + + assert_eq!(b[0][0], 11.); + assert_eq!(b[0][1], 5.5); + assert_eq!(b[1][0], 16.5); + assert_eq!(b[1][1], 22.); +} diff --git a/tests/linear_combination.rs b/tests/linear_combination.rs new file mode 100644 index 0000000..0c3ebac --- /dev/null +++ b/tests/linear_combination.rs @@ -0,0 +1,22 @@ +use matrix::{linear_combination, Vector}; + +#[test] +fn test_linear_combination() { + let e1 = Vector::from([1., 0., 0.]); + let e2 = Vector::from([0., 1., 0.]); + let e3 = Vector::from([0., 0., 1.]); + let v1 = Vector::from([1., 2., 3.]); + let v2 = Vector::from([0., 10., -100.]); + + let a = linear_combination(&[&e1, &e2, &e3], &[10., -2., 0.5]); + + assert_eq!(a[0], 10.); + assert_eq!(a[1], -2.); + assert_eq!(a[2], 0.5); + + let b = linear_combination(&[&v1, &v2], &[10., -2.]); + + assert_eq!(b[0], 10.); + assert_eq!(b[1], 0.); + assert_eq!(b[2], 230.); +} diff --git a/tests/linear_map.rs b/tests/linear_map.rs new file mode 100644 index 0000000..d472a27 --- /dev/null +++ b/tests/linear_map.rs @@ -0,0 +1,46 @@ +use matrix::{Matrix, Vector}; + +#[test] +fn test_linear_map() { + let u = Matrix::from([[1., 0.], [0., 1.]]); + let v = Vector::from([4., 2.]); + let a = u.mul_vec(&v); + assert_eq!(a[0], 4.); + assert_eq!(a[1], 2.); + + let u = Matrix::from([[2., 0.], [0., 2.]]); + let v = Vector::from([4., 2.]); + let a = u.mul_vec(&v); // [8.] // [4.] + assert_eq!(a[0], 8.); + assert_eq!(a[1], 4.); + + let u = Matrix::from([[2., -2.], [-2., 2.]]); + let v = Vector::from([4., 2.]); + let a = u.mul_vec(&v); // [4.] // [-4.] + assert_eq!(a[0], 4.); + assert_eq!(a[1], -4.); + + let u = Matrix::from([[1., 0.], [0., 1.]]); + let v = Matrix::from([[1., 0.], [0., 1.]]); + let a = u.mul_mat(&v); // [1., 0.] // [0., 1.] + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 0.); + assert_eq!(a[1][0], 0.); + assert_eq!(a[1][1], 1.); + + let u = Matrix::from([[1., 0.], [0., 1.]]); + let v = Matrix::from([[2., 1.], [4., 2.]]); + let a = u.mul_mat(&v); // [2., 1.] // [4., 2.] + assert_eq!(a[0][0], 2.); + assert_eq!(a[0][1], 1.); + assert_eq!(a[1][0], 4.); + assert_eq!(a[1][1], 2.); + + let u = Matrix::from([[3., -5.], [6., 8.]]); + let v = Matrix::from([[2., 1.], [4., 2.]]); + let a = u.mul_mat(&v); // [-14., -7.] // [44., 22.] + assert_eq!(a[0][0], -14.); + assert_eq!(a[0][1], -7.); + assert_eq!(a[1][0], 44.); + assert_eq!(a[1][1], 22.); +} diff --git a/tests/norm.rs b/tests/norm.rs new file mode 100644 index 0000000..96082ce --- /dev/null +++ b/tests/norm.rs @@ -0,0 +1,33 @@ +use matrix::Vector; + +macro_rules! approx_eq { + ($a: expr, $b: expr) => { + ($a - $b).abs() < 1e-6 + }; +} + +#[test] +fn test_norm() { + let u = Vector::from([0., 0., 0.]); + assert_eq!(u.norm_1(), 0.); + + assert_eq!(u.norm(), 0.); + + assert_eq!(u.norm_inf(), 0.); + + let u = Vector::from([1., 2., 3.]); + + assert_eq!(u.norm_1(), 6.); + + assert!(approx_eq!(u.norm(), 3.74165738_f64)); + + assert_eq!(u.norm_inf(), 3.); + + let u = Vector::from([-1., -2.]); + + assert_eq!(u.norm_1(), 3.); + + assert!(approx_eq!(u.norm(), 2.236067977_f64)); + + assert_eq!(u.norm_inf(), 2.); +} diff --git a/tests/projections.rs b/tests/projections.rs new file mode 100644 index 0000000..d90e55f --- /dev/null +++ b/tests/projections.rs @@ -0,0 +1,28 @@ +use std::io::Write; + +use matrix::projection; + +use std::fs::File; + +#[test] +fn test_projection() { + let fov = 20.; + let ratio = 3. / 2.; + let near = 2.; + let far = 50.; + + let mat = projection(fov, ratio, near, far); + + let mut file = + File::create("matrix_display/proj").expect("Failed to create newline"); + + for row in mat.iter() { + for i in 0..row.len() { + write!(file, "{}", row[i]).expect("Failed to write newline"); + if i != row.len() - 1 { + write!(file, ", ").expect("Failed to write newline"); + } + } + writeln!(file).expect("Failed to write newline"); + } +} diff --git a/tests/rank.rs b/tests/rank.rs new file mode 100644 index 0000000..140aca0 --- /dev/null +++ b/tests/rank.rs @@ -0,0 +1,24 @@ +use matrix::Matrix; + +#[test] +fn test_rank() { + let u = Matrix::from([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]); + let a = u.rank(); // 3 + assert_eq!(a, 3); + // + let u = + Matrix::from([[1., 2., 0., 0.], [2., 4., 0., 0.], [-1., 2., 1., 1.]]); + let a = u.rank(); // 2 + // + println!("rank {:?}", a); + assert_eq!(a, 2); + + let u = Matrix::from([ + [8., 5., -2.], + [4., 7., 20.], + [7., 6., 1.], + [21., 18., 7.], + ]); + let a = u.rank(); // 3 + assert_eq!(a, 3); +} diff --git a/tests/row_echelon.rs b/tests/row_echelon.rs new file mode 100644 index 0000000..b77a23b --- /dev/null +++ b/tests/row_echelon.rs @@ -0,0 +1,60 @@ +use matrix::Matrix; + +macro_rules! approx_eq { + ($a: expr, $b: expr) => { + ($a - $b).abs() < 1e-6 + }; +} + +#[test] +fn test_row_echelon() { + let u = Matrix::from([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]); + let a = u.row_echelon(); + + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 0.); + assert_eq!(a[0][2], 0.); + assert_eq!(a[1][0], 0.); + assert_eq!(a[1][1], 1.); + assert_eq!(a[1][2], 0.); + assert_eq!(a[2][0], 0.); + assert_eq!(a[2][1], 0.); + assert_eq!(a[2][2], 1.); + + let u = Matrix::from([[1., 2.], [3., 4.]]); + let a = u.row_echelon(); + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 0.); + assert_eq!(a[1][0], 0.); + assert_eq!(a[1][1], 1.); + + let u = Matrix::from([[1., 2.], [2., 4.]]); + let a = u.row_echelon(); + + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 2.); + assert_eq!(a[1][0], 0.); + assert_eq!(a[1][1], 0.); + + let u = Matrix::from([ + [8., 5., -2., 4., 28.], + [4., 2.5, 20., 4., -4.], + [8., 5., 1., 4., 17.], + ]); + let a = u.row_echelon(); + assert!(approx_eq!(a[0][0], 1.0_f64)); + assert!(approx_eq!(a[0][1], 0.625_f64)); + assert!(approx_eq!(a[0][2], 0.0_f64)); + assert!(approx_eq!(a[0][3], 0.0_f64)); + assert!(approx_eq!(a[0][4], -12.1666667_f64)); + assert!(approx_eq!(a[1][0], 0.0_f64)); + assert!(approx_eq!(a[1][1], 0.0_f64)); + assert!(approx_eq!(a[1][2], 1.0_f64)); + assert!(approx_eq!(a[1][3], 0.0_f64)); + assert!(approx_eq!(a[1][4], -3.6666667_f64)); + assert!(approx_eq!(a[2][0], 0.0_f64)); + assert!(approx_eq!(a[2][1], 0.0_f64)); + assert!(approx_eq!(a[2][2], 0.0_f64)); + assert!(approx_eq!(a[2][3], 1.0_f64)); + assert!(approx_eq!(a[2][4], 29.5_f64)); +} diff --git a/tests/scl.rs b/tests/scl.rs new file mode 100644 index 0000000..41fc0b4 --- /dev/null +++ b/tests/scl.rs @@ -0,0 +1,16 @@ +use matrix::{Matrix, Vector}; + +#[test] +fn test_scl_main() { + let mut u = Vector::from([2., 3.]); + u.scl(2.); + assert_eq!(u[0], 4.); + assert_eq!(u[1], 6.); + + let mut u = Matrix::from([[1., 2.], [3., 4.]]); + u.scl(2.); + assert_eq!(u[0][0], 2.); + assert_eq!(u[0][1], 4.); + assert_eq!(u[1][0], 6.); + assert_eq!(u[1][1], 8.); +} diff --git a/tests/sub.rs b/tests/sub.rs new file mode 100644 index 0000000..4d0e4c4 --- /dev/null +++ b/tests/sub.rs @@ -0,0 +1,33 @@ +use matrix::{Matrix, Vector}; + +#[test] +fn test_sub() { + let mut u = Vector::from([2., 3.]); + let v = Vector::from([5., 7.]); + u.sub(&v); + assert_eq!(u[0], -3.); + assert_eq!(u[1], -4.); + + let mut u = Matrix::from([[1., 2.], [3., 4.]]); + let v = Matrix::from([[7., 4.], [-2., 2.]]); + u.sub(&v); + assert_eq!(u[0][0], -6.); + assert_eq!(u[0][1], -2.); + assert_eq!(u[1][0], 5.); + assert_eq!(u[1][1], 2.); + + for i in 0..20 { + let size = i; + let mut v = vec![]; + for i in 0..size { + v.push(i as f32); + } + let mut a: Vector = Vector::from(v); + let b = a.clone(); + a.sub(&b); + + for i in 0..size { + assert_eq!(a[i] + b[i], b[i], "a[i] and b[i] must equal") + } + } +} diff --git a/tests/trace.rs b/tests/trace.rs new file mode 100644 index 0000000..44456f7 --- /dev/null +++ b/tests/trace.rs @@ -0,0 +1,16 @@ +use matrix::Matrix; + +#[test] +fn test_trace() { + let u = Matrix::from([[1., 0.], [0., 1.]]); + let a = u.trace(); + assert_eq!(a.unwrap(), 2.); + + let u = Matrix::from([[2., -5., 0.], [4., 3., 7.], [-2., 3., 4.]]); + let a = u.trace(); + assert_eq!(a.unwrap(), 9.); + + let u = Matrix::from([[-2., -8., 4.], [1., -23., 4.], [0., 6., 4.]]); + let a = u.trace(); + assert_eq!(a.unwrap(), -21.); +} diff --git a/tests/transpose.rs b/tests/transpose.rs new file mode 100644 index 0000000..1b8f4dd --- /dev/null +++ b/tests/transpose.rs @@ -0,0 +1,20 @@ +use matrix::Matrix; + +#[test] +fn test_transpose() { + let mat = Matrix::from([[1., 2.], [3., 4.]]); + let a = mat.transpose(); + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 3.); + assert_eq!(a[1][0], 2.); + assert_eq!(a[1][1], 4.); + + let mat = Matrix::from([[1., 2., 3.], [4., 5., 6.]]); + let a = mat.transpose(); + assert_eq!(a[0][0], 1.); + assert_eq!(a[0][1], 4.); + assert_eq!(a[1][0], 2.); + assert_eq!(a[1][1], 5.); + assert_eq!(a[2][0], 3.); + assert_eq!(a[2][1], 6.); +}