srmc/
lib.rs

1//! # SRMC (Small Rust Math Crate)
2//!
3//! `srmc` is a collection of mathematical methods, functions, and other useful tools. The goal is to make a stand alone math focused crate that can be used for a wide variety of applications.
4//! Will be continuously updated and improved as I imporve with the Rust langauge.
5//!
6
7use core::panic;
8/// Creates a vector in numerical order from start to end with steps + 1 elements.
9/// NOTE: Elements of vector should be evenly spaced, but often times last two are not. Working on a fix for it, but if evenly spaced values is vital you may need to remove the final value or choose a different step size.
10///
11/// # Examples
12///
13/// ```
14/// let vec1: Vec<f32> = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
15/// let vec2 = srmc::create_sequential_vec(0.0, 5.0, 5);
16///
17/// assert_eq!(vec1, vec2);
18/// ```
19/// ```
20/// let vec3: Vec<f32> = vec![0.0, 1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0, 13.5, 15.0];
21/// let vec4 = srmc::create_sequential_vec(0.0, 15.0, 10);
22///
23/// assert_eq!(vec3, vec4);
24/// ```
25pub fn create_sequential_vec(start: f32, end: f32, steps: i32) -> Vec<f32> {
26    let mut sequential_vec = Vec::new();
27    let interval = (end - start) / steps as f32;
28    let mut current_num = start;
29    while current_num < end {
30        sequential_vec.push(current_num);
31        current_num = current_num + interval;
32    }
33    sequential_vec.push(end);
34    return sequential_vec;
35}
36
37/// Uses Simpson's Method to evaluate a definite integral. Function to be integrated must take on parameter of type f32, and return a value of type f32.
38/// See the example functions (sin_x and x_cubed) for examples on how to format input.
39///
40/// # Examples
41///
42/// ```
43/// let calculated_ans1 = srmc::simpsons(&srmc::x_cubed, 2.0, 10.0, 4);
44/// let known_ans1 = 2496.0;
45///
46/// assert_eq!(calculated_ans1, known_ans1);
47/// ```
48/// Since this method is not exact, in the following example we are checking the function against the expected output, not the mathematically correct value of 2.
49/// ```
50/// let calculated_ans2 = srmc::simpsons(&srmc::sin_x, 0.0, std::f32::consts::PI, 10);
51/// let known_ans2 = 2.0001094;
52///
53/// assert_eq!(calculated_ans2, known_ans2);
54///
55pub fn simpsons(f: &dyn Fn(f32) -> f32, lower: f32, upper: f32, steps: i32) -> f32 {
56    if lower > upper {
57        panic!("Upper bound must be greater than lower bound.");
58    }
59    if steps % 2 != 0 {
60        panic!("Steps must be an even number.")
61    }
62    let values = create_sequential_vec(lower, upper, steps);
63    let mut ans: f32 = 0.0;
64    let mut index = 0;
65    let max_index = values.len() - 1;
66    while index < values.len() {
67        if index == 0 || index == max_index {
68            ans = ans + f(values[index]);
69            index = index + 1;
70        } else if index % 2 != 0 {
71            ans = ans + f(values[index]) * 4.0;
72            index = index + 1;
73        } else if index % 2 == 0 {
74            ans = ans + f(values[index]) * 2.0;
75            index = index + 1;
76        }
77    }
78    ans = (((upper - lower) / steps as f32) * ans) / 3.0;
79    return ans;
80}
81
82/// Uses The Trapezoidal Rule to evaluate a definite integral. Function to be integrated must take on parameter of type f32, and return a value of type f32.
83/// See the example functions (sin_x and x_cubed) for examples on how to format input.
84///
85/// # Examples
86///
87/// Integrating the function x cubed from 2.0 to 10.0 with a step size of 10000.
88/// ```
89/// let calculated_val = srmc::trapzoidal_rule(&srmc::x_cubed, 2.0, 10.0, 10000);
90/// ```
91/// Integrating the function sin(x) from 2.0 to 10.0 with a step size of 10000.
92/// ```
93/// let calculated_val = srmc::trapzoidal_rule(&srmc::sin_x, 2.0, 10.0, 10000);
94/// ```
95pub fn trapzoidal_rule(f: &dyn Fn(f32) -> f32, lower: f32, upper: f32, steps: i32) -> f32 {
96    if lower > upper {
97        panic!("Upper bound must be greater than lower bound.");
98    }
99    if steps % 2 != 0 {
100        panic!("Steps must be an even number.")
101    }
102    let values = create_sequential_vec(lower, upper, steps);
103    let mut ans: f32 = 0.0;
104    let mut index = 0;
105    let max_index = values.len() - 1;
106    while index < values.len() {
107        if index == 0 || index == max_index {
108            ans = ans + f(values[index]);
109            index = index + 1;
110        } else {
111            ans = ans + f(values[index]) * 2.0;
112            index = index + 1;
113        }
114    }
115    ans = ((upper - lower) / (2.0 * steps as f32)) * ans;
116    return ans;
117}
118
119/// Function to calculate the relative error between two values
120///
121/// # Example
122///
123/// ```
124/// let known_ans = 2.0;
125/// let calculated_ans = srmc::simpsons(&srmc::sin_x, 0.0, std::f32::consts::PI, 10);
126/// let rel_error = srmc::relative_error(calculated_ans, known_ans);
127/// println!("Relative error between known and calculated value is: {}", rel_error);
128/// ```
129///  
130pub fn relative_error(observed_val: f32, true_val: f32) -> f32 {
131    return ((observed_val - true_val).abs()) / true_val;
132}
133
134/// Function to calculate the percentage error between two values
135///
136/// # Example
137///
138/// ```
139/// let known_ans = 2.0;
140/// let calculated_ans = srmc::simpsons(&srmc::sin_x, 0.0, std::f32::consts::PI, 10);
141/// let percentage_error = srmc::percentage_error(calculated_ans, known_ans);
142/// println!("Percentage error between known and calculated value is: {}%", percentage_error);
143/// ```
144///  
145pub fn percentage_error(observed_val: f32, true_val: f32) -> f32 {
146    return (((observed_val - true_val).abs()) / true_val) * 100.0;
147}
148
149/// Example input function for x cubed
150pub fn x_cubed(x: f32) -> f32 {
151    return f32::powf(x, 3.0);
152}
153
154/// Example input function for sin(x)
155pub fn sin_x(x: f32) -> f32 {
156    return x.sin();
157}
158
159/// SRMC Matrix struct with various implementations
160///
161/// Note: When indexing a value or row/column of a Matrix, the numbering starts at zero. So for a 3x3 Matrix,
162/// the top row is the 0th row and the top left value is at position (0, 0).
163///
164/// It is possible to create a matrix that is invalid by having rows of different lengths, there are no checks to ensure
165/// that this is not the case when creating a new Matrix. If you're creating a Matrix directly from a vector of vectors and manually
166/// entering the dimensions, ensure all values are entered properly. Alternatively, there is a "create_matrix" function and
167/// "change_row"/"change_column" implemenations with checks to ensure a matrix is created/changed correctly. To check if a Matrix
168/// was made correctly use the "is_valid" implementation.
169///
170/// Example of creating a Matrix:
171/// ```
172/// let input: Vec<Vec<f32>> = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
173/// let matrix = srmc::Matrix {
174///    elements: input,
175///    rows: 2,
176///    columns: 2,
177///};
178/// assert_eq!(true, matrix.is_valid());
179/// ```
180pub struct Matrix {
181    pub elements: Vec<Vec<f32>>,
182    pub rows: i32,
183    pub columns: i32,
184}
185
186impl Matrix {
187    /// Prints matrix in terminal
188    pub fn print(&self) {
189        for item in &self.elements {
190            println!("{:?}", item);
191        }
192    }
193    /// Returns the dimensions of the matrix as a tuple (rows, columns)
194    pub fn get_dimensions(&self) -> (i32, i32) {
195        return (self.rows, self.columns);
196    }
197
198    /// Checks if a Matrix has rows of equal length and correct values for number of rows and columns
199    pub fn is_valid(&self) -> bool {
200        let row_length = self.elements[0].len();
201        let mut i: usize = 1;
202        while i < self.elements.len() {
203            if self.elements[i].len() != row_length {
204                return false;
205            }
206            i = i + 1;
207        }
208        if self.elements.len() != self.rows as usize {
209            return false;
210        }
211        if self.elements[0].len() != self.columns as usize {
212            return false;
213        }
214        return true;
215    }
216
217    /// Returns the element of the matrix at the given position
218    pub fn index_val(&self, row: i32, column: i32) -> f32 {
219        if &(row + 1) > (&self.rows) || &(column + 1) > (&self.columns) {
220            panic!("Row and Column position outside the bounds matrix. Please provide a value for rows from 0 to {} and a value for columns from 0 to {}", self.rows - 1, self.columns - 1);
221        }
222        let row_num = &self.elements[row as usize];
223        return row_num[column as usize];
224    }
225
226    /// Changes the element of the matrix at the given position
227    pub fn change_val(&mut self, val: f32, row: i32, column: i32) {
228        if &(row + 1) > (&self.rows) || &(column + 1) > (&self.columns) {
229            panic!("Row and Column position outside the bounds matrix. Please provide a value for rows from 0 to {} and a value for columns from 0 to {}", self.rows - 1, self.columns - 1);
230        }
231        self.elements[row as usize][column as usize] = val;
232    }
233
234    /// Multiplies entire matrix by some value
235    pub fn multiply_by_value(&mut self, val: f32) {
236        let mut i: usize = 0;
237        while i < self.rows as usize {
238            let mut j = 0;
239            while j < self.columns as usize {
240                self.elements[i][j] = self.elements[i][j] * val;
241                j = j + 1;
242            }
243            i = i + 1;
244        }
245    }
246
247    /// Changes given row to input vector, input vector must match dimensions of the matrix
248    ///
249    /// Example:
250    /// ```
251    /// let mut matrix = srmc::create_matrix(2, 3);
252    /// let new_row = vec![1.0, 2.0, 3.0];
253    /// matrix.change_row(0, new_row);
254    /// ```
255    pub fn change_row(&mut self, row: i32, new_row: Vec<f32>) {
256        if new_row.len() != self.elements[row as usize].len() {
257            panic!("Input vector not matching matrix dimensions.");
258        }
259        self.elements[row as usize] = new_row;
260    }
261
262    /// Changes given column to input vector, input vector must match dimensions of the matrix
263    ///
264    /// Example:
265    /// ```
266    /// let mut matrix = srmc::create_matrix(2, 3);
267    /// let new_column = vec![1.0, 2.0];
268    /// matrix.change_column(0, new_column);
269    /// ```
270    pub fn change_column(&mut self, column: i32, new_column: Vec<f32>) {
271        if new_column.len() != self.elements.len() {
272            panic!("Input vector not matching matrix dimensions.");
273        }
274        let mut i: usize = 0;
275        while i < self.elements.len() {
276            self.elements[i][column as usize] = new_column[i];
277            i = i + 1;
278        }
279    }
280}
281
282/// Returns an SRMC Matrix swith provided dimensions. All elements of matrix are initially set to zero.
283///
284/// Creates 3x3 Matrix
285/// ```
286/// let matrix = srmc::create_matrix(3, 3);
287/// ```
288/// Creates 1x5 Matrix
289/// ```
290/// let matrix = srmc::create_matrix(1, 5);
291/// ```
292pub fn create_matrix(rows: i32, columns: i32) -> Matrix {
293    return Matrix {
294        elements: vec![vec![0.0; columns as usize]; rows as usize],
295        rows,
296        columns,
297    };
298}
299
300/// Returns an SRMC Matrix with provided dimensions. Elements of the matrix start at 1.0 and are incremented by 1.
301/// ```
302/// let matrix = srmc::create_numbered_matrix(2, 4);
303/// matrix.print();
304/// // The following matrix is printed when .print() is called:
305/// //[1.0, 2.0, 3.0, 4.0]
306/// //[5.0, 6.0, 7.0, 8.0]
307/// ```
308pub fn create_numbered_matrix(rows: i32, columns: i32) -> Matrix {
309    let mut num_matrix = create_matrix(rows, columns);
310    let mut val: f32 = 0.0;
311    let mut i: usize = 0;
312    while i < rows as usize {
313        let mut j: usize = 0;
314        while j < columns as usize {
315            val = val + 1.0;
316            num_matrix.elements[i][j] = val;
317            j = j + 1;
318        }
319        i = i + 1;
320    }
321    return num_matrix;
322}
323
324/// Returns an SRMC Matrix with diagonal values set to one.
325///
326/// Crates 3x3 identity matrix
327/// ```
328/// let matrix = srmc::create_identity_matrix(3);
329/// ```
330/// Creates 10x10 identity matrix
331/// ```
332/// let matrix = srmc::create_identity_matrix(10);
333/// ```
334pub fn create_identity_matrix(dimension: i32) -> Matrix {
335    let mut res_mat = create_matrix(dimension, dimension);
336    let mut i: usize = 0;
337    while i < dimension as usize {
338        res_mat.elements[i][i] = 1.0;
339        i = i + 1;
340    }
341    return res_mat;
342}
343
344/// Multiplies two Matricies together and returns the result as an SRMC Matrix.
345/// ```
346/// let matrix1 = srmc::create_numbered_matrix(3, 2);
347/// let matrix2 = srmc::create_numbered_matrix(2, 2);
348/// let matrix3 = srmc::matrix_multiplication(&matrix1, &matrix2);
349/// ```
350/// NOTE: Running this function keeps input Matricies unchanged and creates a new Matrix.
351/// If you want to run without creating a new Matrix do the following:
352/// ```
353/// let mut matrix1 = srmc::create_numbered_matrix(3, 2);
354/// let matrix2 = srmc::create_numbered_matrix(2, 2);
355/// matrix1 = srmc::matrix_multiplication(&matrix1, &matrix2);
356/// ```
357/// In the above code, matrix2 could have also been mutable and set equal to the result.
358pub fn matrix_multiplication(mat1: &Matrix, mat2: &Matrix) -> Matrix {
359    if mat1.columns != mat2.rows {
360        panic!("Matricies have incompatible dimensions.");
361    }
362    let mut res_mat = create_matrix(mat1.rows, mat2.columns);
363    let mut i: usize = 0;
364    while i < mat1.rows as usize {
365        let mut j: usize = 0;
366        while j < mat2.columns as usize {
367            let mut k: usize = 0;
368            while k < mat2.rows as usize {
369                res_mat.elements[i][j] += mat1.elements[i][k] * mat2.elements[k][j];
370                k = k + 1;
371            }
372            j = j + 1;
373        }
374        i = i + 1;
375    }
376    return res_mat;
377}
378
379/// Returns the determinant of a 2x2 Matrix.
380/// ```
381/// let matrix = srmc::create_numbered_matrix(2, 2);
382/// let determinant = srmc::matrix_2x2det(&matrix);
383/// ```
384pub fn matrix_2x2det(mat: &Matrix) -> f32 {
385    if mat.rows != 2 || mat.columns != 2 {
386        panic!("Matrix must be of size 2x2.");
387    }
388    return (mat.elements[0][0] * mat.elements[1][1]) - (mat.elements[0][1] * mat.elements[1][0]);
389}
390
391/// Returns the determinant of a 3x3 Matrix.
392/// ```
393/// let matrix = srmc::create_numbered_matrix(3, 3);
394/// let determinant = srmc::matrix_3x3det(&matrix);
395/// ```
396pub fn matrix_3x3det(mat: &Matrix) -> f32 {
397    if mat.rows != 3 || mat.columns != 3 {
398        panic!("Matrix must be of size 3x3.");
399    }
400    let val1: f32 =
401        (mat.elements[1][1] * mat.elements[2][2]) - (mat.elements[1][2] * mat.elements[2][1]);
402    let val2: f32 =
403        (mat.elements[1][0] * mat.elements[2][2]) - (mat.elements[1][2] * mat.elements[2][0]);
404    let val3: f32 =
405        (mat.elements[1][0] * mat.elements[2][1]) - (mat.elements[1][1] * mat.elements[2][0]);
406    return (mat.elements[0][0] * val1) - (mat.elements[0][1] * val2) + (mat.elements[0][2] * val3);
407}
408
409/// Adds two Matricies together and returns the result as an SRMC Matrix
410/// ```
411/// let matrix1 = srmc::create_numbered_matrix(3,3);
412/// let matrix2 = srmc::create_numbered_matrix(3,3);
413/// let matrix3 = srmc::matrix_addition(&matrix1, &matrix2);
414/// ```
415/// NOTE: Running this function keeps input Matricies unchanged and creates a new Matrix.
416/// If you want to run without creating a new Matrix do the following:
417/// ```
418/// let mut matrix1 = srmc::create_numbered_matrix(3,3);
419/// let matrix2 = srmc::create_numbered_matrix(3,3);
420/// matrix1 = srmc::matrix_addition(&matrix1, &matrix2);
421/// ```
422/// In the above code, matrix2 could have also been mutable and set equal to the result.
423pub fn matrix_addition(mat1: &Matrix, mat2: &Matrix) -> Matrix {
424    if mat1.rows != mat2.rows || mat1.columns != mat2.columns {
425        panic!("Matricies must be of the same size to perform matrix addition.");
426    }
427    let mut res_mat = create_matrix(mat1.rows, mat1.columns);
428
429    let mut i: usize = 0;
430    while i < mat1.rows as usize {
431        let mut j = 0;
432        while j < mat1.columns as usize {
433            res_mat.elements[i][j] = mat1.elements[i][j] + mat2.elements[i][j];
434            j = j + 1;
435        }
436        i = i + 1;
437    }
438    return res_mat;
439}
440
441/// Subtracts two Matricies and returns the result as an SRMC Matrix.
442///
443/// As matricies do not commute, input order is important. For this the function
444/// the second argument is subtracted from the first. So in the following code
445/// matrix3 = matrix1 - matrix2.
446/// ```
447/// let matrix1 = srmc::create_numbered_matrix(3,3);
448/// let matrix2 = srmc::create_identity_matrix(3);
449/// let matrix3 = srmc::matrix_subtraction(&matrix1, &matrix2);
450/// ```
451/// NOTE: Running this function keeps input Matricies unchanged and creates a new Matrix.
452/// If you want to run without creating a new Matrix do the following:
453/// ```
454/// let mut matrix1 = srmc::create_numbered_matrix(3,3);
455/// let matrix2 = srmc::create_identity_matrix(3);
456/// matrix1 = srmc::matrix_subtraction(&matrix1, &matrix2);
457/// ```
458/// In the above code, matrix2 could have also been mutable and set equal to the result.
459pub fn matrix_subtraction(mat1: &Matrix, mat2: &Matrix) -> Matrix {
460    if mat1.rows != mat2.rows || mat1.columns != mat2.columns {
461        panic!("Matricies must be of the same size to perform matrix subtraction.");
462    }
463    let mut res_mat = create_matrix(mat1.rows, mat1.columns);
464
465    let mut i: usize = 0;
466    while i < mat1.rows as usize {
467        let mut j = 0;
468        while j < mat1.columns as usize {
469            res_mat.elements[i][j] = mat1.elements[i][j] - mat2.elements[i][j];
470            j = j + 1;
471        }
472        i = i + 1;
473    }
474    return res_mat;
475}
476
477/// Returns the tranpose of an SRMC Matrix
478///
479/// Example Code:
480/// ```
481/// let matrix = srmc::create_numbered_matrix(2,4);
482/// let matrix2 = srmc::matrix_transpose(&matrix);
483/// ```
484/// NOTE: Running this function keeps input Matrix unchanged and creates a new Matrix.
485/// If you want to change input Matrix without creating a new one do the following:
486/// ```
487/// let mut matrix = srmc::create_numbered_matrix(2,4);
488/// matrix = srmc::matrix_transpose(&matrix);
489///```
490pub fn matrix_transpose(mat: &Matrix) -> Matrix {
491    let mut res_mat = create_matrix(mat.columns, mat.rows);
492    let mut i: usize = 0;
493    while i < mat.columns as usize {
494        let mut j: usize = 0;
495        while j < mat.rows as usize {
496            res_mat.elements[i][j] = mat.elements[j][i];
497            j = j + 1;
498        }
499        i = i + 1;
500    }
501    return res_mat;
502}