use nalgebra::{DMatrix, DVector};
use crate::error::SimError;
pub struct MnaSystem {
pub a: DMatrix<f64>,
pub b: DVector<f64>,
pub num_nodes: usize,
pub num_vsources: usize,
}
impl MnaSystem {
pub fn new(num_nodes: usize, num_vsources: usize) -> Self {
let size = num_nodes + num_vsources;
Self {
a: DMatrix::zeros(size, size),
b: DVector::zeros(size),
num_nodes,
num_vsources,
}
}
pub fn size(&self) -> usize {
self.num_nodes + self.num_vsources
}
pub fn solve(&self) -> Result<DVector<f64>, SimError> {
let solution = self
.a
.clone()
.lu()
.solve(&self.b)
.ok_or(SimError::SingularMatrix)?;
for i in 0..solution.len() {
if solution[i].is_nan() || solution[i].is_infinite() {
return Err(SimError::InvalidSolution);
}
}
Ok(solution)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use nalgebra::DMatrix;
#[test]
fn solve_2x2_single_resistor() {
let mut sys = MnaSystem::new(1, 1);
sys.a[(0, 0)] = 0.001;
sys.a[(0, 1)] = 1.0;
sys.a[(1, 0)] = 1.0;
sys.b[1] = 5.0;
let x = sys.solve().expect("should solve valid 2x2 system");
assert_relative_eq!(x[0], 5.0, epsilon = 1e-10);
assert_relative_eq!(x[1], -0.005, epsilon = 1e-10);
}
#[test]
fn solve_3x3_voltage_divider() {
let mut sys = MnaSystem::new(2, 1);
sys.a[(0, 0)] += 0.001;
sys.a[(1, 1)] += 0.001;
sys.a[(0, 1)] -= 0.001;
sys.a[(1, 0)] -= 0.001;
sys.a[(1, 1)] += 0.0005;
sys.a[(0, 2)] = 1.0;
sys.a[(2, 0)] = 1.0;
sys.b[2] = 10.0;
let x = sys.solve().expect("should solve valid 3x3 system");
assert_relative_eq!(x[0], 10.0, epsilon = 1e-10);
assert_relative_eq!(x[1], 20.0 / 3.0, epsilon = 1e-10);
assert_relative_eq!(x[2], -10.0 / 3000.0, epsilon = 1e-10);
}
#[test]
fn solve_singular_matrix_returns_error() {
let sys = MnaSystem::new(1, 1);
let result = sys.solve();
assert!(result.is_err());
match result.unwrap_err() {
crate::error::SimError::SingularMatrix => {} other => panic!("expected SingularMatrix, got: {other}"),
}
}
#[test]
fn new_creates_zero_filled_system() {
let sys = MnaSystem::new(3, 2);
assert_eq!(sys.size(), 5);
assert_eq!(sys.a.nrows(), 5);
assert_eq!(sys.a.ncols(), 5);
assert_eq!(sys.b.len(), 5);
assert_eq!(sys.a, DMatrix::zeros(5, 5));
}
#[test]
fn solve_well_conditioned_returns_ok() {
let mut sys = MnaSystem::new(3, 0);
sys.a[(0, 0)] = 1.0;
sys.a[(1, 1)] = 1.0;
sys.a[(2, 2)] = 1.0;
sys.b[0] = 1.0;
sys.b[1] = 2.0;
sys.b[2] = 3.0;
let x = sys.solve().expect("identity system should solve");
assert_relative_eq!(x[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(x[1], 2.0, epsilon = 1e-10);
assert_relative_eq!(x[2], 3.0, epsilon = 1e-10);
}
}