1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! Engine errors. Deck-level problems surface as
//! `dune_netlist::Diagnostic`s during elaboration; these are the
//! failures of building or running a circuit.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Bad programmatic construction (duplicate name, zero-valued
/// resistor, dangling reference, …).
#[error("{0}")]
Build(String),
/// The deck uses a device the engine does not implement yet.
#[error("`{name}`: {what} is not implemented yet")]
Unsupported { name: String, what: String },
/// The deck could not be elaborated into a circuit.
#[error("{0}")]
Elab(String),
/// Singular MNA matrix (floating node, voltage-source loop, …).
#[error("singular matrix: check node `{0}`")]
Singular(String),
/// `NIiter` gave up (`E_ITERLIM`).
#[error("too many iterations without convergence")]
IterLim,
/// The transient step shrank below `delmin` (`E_TIMESTEP`).
#[error("timestep too small at t = {time:.6e}")]
Timestep { time: f64 },
/// The transient attempted more internal timepoints than
/// [`Options::max_tran_steps`](crate::Options::max_tran_steps)
/// allows — Dune's guard against runaway breakpoint cascades
/// (D-0022); neither the 3f5 nor ngspice enforces `ITL5`.
#[error("transient exceeded {steps} internal steps at t = {time:.6e}")]
StepLimit { steps: usize, time: f64 },
/// `.pss`: the shooting iterations did not bring the period
/// mismatch `x(t₀+T) − x(t₀)` within tolerance. `residuals` holds
/// each iteration's mismatch in units of the tolerance
/// ([`Pss::residuals`](crate::Pss::residuals)). `period` is the
/// last period iterate of an autonomous oscillator (where `T` is
/// a Newton unknown); `None` for a driven circuit, whose period
/// is fixed.
#[error(
"periodic steady state did not converge in {iterations} shooting iterations \
(residual/tolerance per iteration: {}){}",
list_e(.residuals),
last_period(.period)
)]
PssConvergence {
iterations: usize,
residuals: Vec<f64>,
period: Option<f64>,
},
/// An output selection named a vector the solution does not have.
#[error("no such vector `{0}`")]
UnknownVector(String),
}
/// `; last period 1.234e-6 s` — an autonomous shooting's last `T`.
fn last_period(p: &Option<f64>) -> String {
p.map_or_else(String::new, |t| format!("; last period {t:.6e} s"))
}
/// `1.234e-3, 5.000e-1` — a residual history in an error message.
fn list_e(v: &[f64]) -> String {
v.iter()
.map(|r| format!("{r:.3e}"))
.collect::<Vec<_>>()
.join(", ")
}