mathhook_core/core/matrix/
numeric_matrix.rs1use crate::error::MathError;
2
3mod arithmetic;
4mod conversion;
5mod decomposition;
6mod display;
7mod multiply;
8mod solve;
9
10pub use decomposition::LUResult;
11
12const EPSILON: f64 = 1e-10;
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct NumericMatrix {
16 rows: usize,
17 cols: usize,
18 data: Vec<f64>,
19}
20
21impl NumericMatrix {
22 pub fn zeros(rows: usize, cols: usize) -> Result<Self, MathError> {
23 if rows == 0 || cols == 0 {
24 return Err(MathError::DomainError {
25 operation: "NumericMatrix::zeros".to_string(),
26 value: crate::Expression::integer(0),
27 reason: "Matrix dimensions must be positive".to_string(),
28 });
29 }
30 Ok(Self {
31 rows,
32 cols,
33 data: vec![0.0; rows * cols],
34 })
35 }
36
37 pub fn identity(n: usize) -> Result<Self, MathError> {
38 if n == 0 {
39 return Err(MathError::DomainError {
40 operation: "NumericMatrix::identity".to_string(),
41 value: crate::Expression::integer(0),
42 reason: "Matrix dimension must be positive".to_string(),
43 });
44 }
45 let mut data = vec![0.0; n * n];
46 for i in 0..n {
47 data[i * n + i] = 1.0;
48 }
49 Ok(Self {
50 rows: n,
51 cols: n,
52 data,
53 })
54 }
55
56 pub fn from_flat(rows: usize, cols: usize, data: Vec<f64>) -> Result<Self, MathError> {
57 if rows == 0 || cols == 0 {
58 return Err(MathError::DomainError {
59 operation: "NumericMatrix::from_flat".to_string(),
60 value: crate::Expression::integer(0),
61 reason: "Matrix dimensions must be positive".to_string(),
62 });
63 }
64 if data.len() != rows * cols {
65 return Err(MathError::DomainError {
66 operation: "NumericMatrix::from_flat".to_string(),
67 value: crate::Expression::integer(data.len() as i64),
68 reason: format!(
69 "Data length {} does not match dimensions {}x{}",
70 data.len(),
71 rows,
72 cols
73 ),
74 });
75 }
76 Ok(Self { rows, cols, data })
77 }
78
79 pub fn from_fn<F>(rows: usize, cols: usize, mut f: F) -> Result<Self, MathError>
80 where
81 F: FnMut(usize, usize) -> f64,
82 {
83 if rows == 0 || cols == 0 {
84 return Err(MathError::DomainError {
85 operation: "NumericMatrix::from_fn".to_string(),
86 value: crate::Expression::integer(0),
87 reason: "Matrix dimensions must be positive".to_string(),
88 });
89 }
90 let mut data = Vec::with_capacity(rows * cols);
91 for i in 0..rows {
92 for j in 0..cols {
93 data.push(f(i, j));
94 }
95 }
96 Ok(Self { rows, cols, data })
97 }
98
99 pub fn dimensions(&self) -> (usize, usize) {
100 (self.rows, self.cols)
101 }
102
103 pub fn get(&self, row: usize, col: usize) -> Result<f64, MathError> {
104 if row >= self.rows || col >= self.cols {
105 return Err(MathError::DomainError {
106 operation: "NumericMatrix::get".to_string(),
107 value: crate::Expression::integer((row * self.cols + col) as i64),
108 reason: format!(
109 "Index ({}, {}) out of bounds for {}x{} matrix",
110 row, col, self.rows, self.cols
111 ),
112 });
113 }
114 Ok(self.data[row * self.cols + col])
115 }
116
117 pub fn set(&mut self, row: usize, col: usize, value: f64) -> Result<(), MathError> {
118 if row >= self.rows || col >= self.cols {
119 return Err(MathError::DomainError {
120 operation: "NumericMatrix::set".to_string(),
121 value: crate::Expression::integer((row * self.cols + col) as i64),
122 reason: format!(
123 "Index ({}, {}) out of bounds for {}x{} matrix",
124 row, col, self.rows, self.cols
125 ),
126 });
127 }
128 self.data[row * self.cols + col] = value;
129 Ok(())
130 }
131
132 pub fn is_square(&self) -> bool {
133 self.rows == self.cols
134 }
135
136 pub fn is_symmetric(&self) -> bool {
137 if !self.is_square() {
138 return false;
139 }
140 for i in 0..self.rows {
141 for j in (i + 1)..self.cols {
142 let diff = (self.data[i * self.cols + j] - self.data[j * self.cols + i]).abs();
143 if diff > EPSILON {
144 return false;
145 }
146 }
147 }
148 true
149 }
150
151 pub fn transpose(&self) -> Self {
152 let mut data = vec![0.0; self.rows * self.cols];
153 for i in 0..self.rows {
154 for j in 0..self.cols {
155 data[j * self.rows + i] = self.data[i * self.cols + j];
156 }
157 }
158 Self {
159 rows: self.cols,
160 cols: self.rows,
161 data,
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_zeros() {
172 let m = NumericMatrix::zeros(2, 3).unwrap();
173 assert_eq!(m.dimensions(), (2, 3));
174 assert_eq!(m.get(0, 0).unwrap(), 0.0);
175 assert_eq!(m.get(1, 2).unwrap(), 0.0);
176 }
177
178 #[test]
179 fn test_identity() {
180 let m = NumericMatrix::identity(3).unwrap();
181 assert_eq!(m.dimensions(), (3, 3));
182 assert_eq!(m.get(0, 0).unwrap(), 1.0);
183 assert_eq!(m.get(1, 1).unwrap(), 1.0);
184 assert_eq!(m.get(2, 2).unwrap(), 1.0);
185 assert_eq!(m.get(0, 1).unwrap(), 0.0);
186 }
187
188 #[test]
189 fn test_from_flat() {
190 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
191 let m = NumericMatrix::from_flat(2, 3, data).unwrap();
192 assert_eq!(m.dimensions(), (2, 3));
193 assert_eq!(m.get(0, 0).unwrap(), 1.0);
194 assert_eq!(m.get(1, 2).unwrap(), 6.0);
195 }
196
197 #[test]
198 fn test_from_fn() {
199 let m = NumericMatrix::from_fn(2, 2, |i, j| (i * 2 + j) as f64).unwrap();
200 assert_eq!(m.get(0, 0).unwrap(), 0.0);
201 assert_eq!(m.get(0, 1).unwrap(), 1.0);
202 assert_eq!(m.get(1, 0).unwrap(), 2.0);
203 assert_eq!(m.get(1, 1).unwrap(), 3.0);
204 }
205
206 #[test]
207 fn test_transpose() {
208 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
209 let m = NumericMatrix::from_flat(2, 3, data).unwrap();
210 let t = m.transpose();
211 assert_eq!(t.dimensions(), (3, 2));
212 assert_eq!(t.get(0, 0).unwrap(), 1.0);
213 assert_eq!(t.get(1, 0).unwrap(), 2.0);
214 assert_eq!(t.get(2, 1).unwrap(), 6.0);
215 }
216
217 #[test]
218 fn test_is_symmetric() {
219 let sym_data = vec![1.0, 2.0, 2.0, 1.0];
220 let m = NumericMatrix::from_flat(2, 2, sym_data).unwrap();
221 assert!(m.is_symmetric());
222
223 let nonsym_data = vec![1.0, 2.0, 3.0, 1.0];
224 let m = NumericMatrix::from_flat(2, 2, nonsym_data).unwrap();
225 assert!(!m.is_symmetric());
226 }
227
228 #[test]
229 fn test_invalid_dimensions() {
230 assert!(NumericMatrix::zeros(0, 3).is_err());
231 assert!(NumericMatrix::identity(0).is_err());
232 assert!(NumericMatrix::from_flat(2, 3, vec![1.0, 2.0]).is_err());
233 }
234
235 #[test]
236 fn test_out_of_bounds() {
237 let m = NumericMatrix::zeros(2, 2).unwrap();
238 assert!(m.get(2, 0).is_err());
239 assert!(m.get(0, 2).is_err());
240 }
241}