use thiserror::Error;
#[derive(Debug, Error)]
pub enum SimError {
#[error(
"No ground node defined in circuit. Every circuit needs a ground (reference) node \
— try connecting a component terminal to node \"0\"."
)]
NoGround,
#[error(
"These nodes are not connected to the rest of the circuit: {0:?}. \
Make sure every node has a path to ground through components."
)]
DisconnectedNodes(Vec<String>),
#[error(
"Node \"{0}\" has fewer than 2 connections and cannot carry current. \
Connect it to at least 2 components."
)]
FloatingNode(String),
#[error(
"Circuit matrix is singular — the circuit has no unique solution. \
Common causes: voltage sources in a loop, or a subcircuit disconnected from ground."
)]
SingularMatrix,
#[error(
"Solver produced invalid values (NaN or Infinity). This usually means component \
values span an extreme range. Try checking for very small or very large values."
)]
InvalidSolution,
#[error("Invalid component: {0}")]
InvalidComponent(String),
#[error(
"Resistor \"{0}\" has zero or negative resistance, which is not physically meaningful."
)]
InvalidResistance(String),
#[error(
"Newton-Raphson failed to converge after {iterations} iterations \
(max node-voltage step {max_step_volts:.3e} V). \
The circuit may have no valid operating point."
)]
ConvergenceFailed {
iterations: usize,
max_step_volts: f64,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn convergence_failed_carries_diagnostics() {
let err = SimError::ConvergenceFailed {
iterations: 100,
max_step_volts: 1.234e-3,
};
match err {
SimError::ConvergenceFailed {
iterations,
max_step_volts,
} => {
assert_eq!(iterations, 100);
assert!((max_step_volts - 1.234e-3).abs() < 1e-12);
}
_ => panic!("expected ConvergenceFailed variant"),
}
}
#[test]
fn convergence_failed_display_includes_iterations_and_step() {
let err = SimError::ConvergenceFailed {
iterations: 42,
max_step_volts: 7.5e-4,
};
let msg = err.to_string();
assert!(
msg.contains("42 iterations"),
"Display should mention iteration count: {msg}"
);
assert!(
msg.contains("7.500e-4") || msg.contains("step"),
"Display should mention step size: {msg}"
);
}
#[test]
fn other_variants_still_format_cleanly() {
assert!(SimError::NoGround.to_string().contains("ground"));
assert!(SimError::SingularMatrix.to_string().contains("singular"));
assert!(SimError::DisconnectedNodes(vec!["n1".into(), "n2".into()])
.to_string()
.contains("n1"));
}
}