1use dune_raw::{Data, VarType};
14
15use crate::circuit::{Circuit, Spec, VarKind};
16use crate::engine::Engine;
17use crate::error::Error;
18use crate::matrix::{AnKind, Var};
19use crate::options::Options;
20use crate::solution::Solution;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum OutputVar {
26 Voltage {
28 node: String,
29 reference: Option<String>,
30 },
31 Current { vsrc: String },
34}
35
36#[derive(Clone, Debug)]
41pub struct Tf {
42 pub solution: Solution,
43 pub transfer_function: f64,
45 pub input_impedance: f64,
47 pub output_impedance: f64,
49 pub notes: Vec<String>,
50}
51
52#[derive(Clone, Copy, Debug)]
54pub(crate) enum OutRef {
55 Volt(Var, Var),
56 Branch(Var),
57}
58
59impl OutRef {
60 pub fn read(&self, x: &[f64]) -> f64 {
61 match *self {
62 OutRef::Volt(p, n) => x[p] - x[n],
63 OutRef::Branch(b) => x[b],
64 }
65 }
66}
67
68pub(crate) fn resolve_output(
71 engine: &Engine,
72 output: &OutputVar,
73 what: &str,
74) -> Result<OutRef, Error> {
75 let node = |name: &str| -> Result<Var, Error> {
76 let name = name.to_ascii_lowercase();
77 if name == "0" {
78 return Ok(0);
79 }
80 engine
81 .vars
82 .names
83 .iter()
84 .zip(&engine.vars.kinds)
85 .position(|(n, k)| *n == name && !matches!(k, VarKind::Branch))
86 .ok_or_else(|| Error::Build(format!("{what} names unknown node `{name}`")))
87 };
88 match output {
89 OutputVar::Voltage { node: p, reference } => Ok(OutRef::Volt(
90 node(p)?,
91 match reference {
92 Some(r) => node(r)?,
93 None => 0,
94 },
95 )),
96 OutputVar::Current { vsrc } => Ok(OutRef::Branch(branch_of(engine, vsrc).ok_or_else(
97 || Error::Build(format!("{what} output `{vsrc}` has no branch current")),
98 )?)),
99 }
100}
101
102fn branch_of(engine: &Engine, name: &str) -> Option<Var> {
104 let name = name.to_ascii_lowercase();
105 engine
106 .vars
107 .names
108 .iter()
109 .zip(&engine.vars.kinds)
110 .position(|(n, k)| *n == name && matches!(k, VarKind::Branch))
111}
112
113enum Input {
115 V(Var),
116 I(Var, Var),
117}
118
119impl Circuit {
120 pub fn tf(&self, opts: &Options, output: &OutputVar, insrc: &str) -> Result<Tf, Error> {
122 let insrc = insrc.to_ascii_lowercase();
123 let mut input_nodes = None;
125 for spec in &self.specs {
126 match spec {
127 Spec::Vsource(v) if v.name == insrc => input_nodes = Some(None),
128 Spec::Isource(i) if i.name == insrc => {
129 input_nodes = Some(Some(i.nodes.clone()));
130 }
131 _ => {}
132 }
133 }
134 let Some(input_nodes) = input_nodes else {
135 let known = self.specs.iter().any(|s| s.name() == insrc);
136 return Err(Error::Build(if known {
137 format!("transfer function source `{insrc}` not of proper type")
138 } else {
139 format!("transfer function source `{insrc}` not in circuit")
140 }));
141 };
142
143 let built = self.build(opts)?;
144 let notes = built.notes.clone();
145 let mut engine = Engine::new(built);
146 engine.set_kind(AnKind::DcOp, false);
147 engine.op(opts)?;
148
149 let out = resolve_output(&engine, output, ".tf")?;
150 let var_of = |name: &str| -> Var {
151 if name == "0" {
152 return 0;
153 }
154 engine
155 .vars
156 .names
157 .iter()
158 .zip(&engine.vars.kinds)
159 .position(|(n, k)| n == name && !matches!(k, VarKind::Branch))
160 .unwrap_or(0)
161 };
162 let input = match &input_nodes {
163 Some([n1, n2]) => Input::I(var_of(n1), var_of(n2)),
164 None => Input::V(branch_of(&engine, &insrc).ok_or_else(|| {
165 Error::Build(format!("transfer function source `{insrc}` has no branch"))
166 })?),
167 };
168
169 let n = engine.vars.len();
170 let mut rhs = vec![0.0; n];
171 match input {
172 Input::I(n1, n2) => {
173 rhs[n1] -= 1.0;
174 rhs[n2] += 1.0;
175 }
176 Input::V(br) => rhs[br] += 1.0,
177 }
178 engine.solve_factored(&mut rhs)?;
179 let transfer_function = out.read(&rhs);
180 let input_impedance = match input {
181 Input::I(n1, n2) => rhs[n2] - rhs[n1],
182 Input::V(br) => {
183 if rhs[br].abs() < 1e-20 {
184 1e20
185 } else {
186 -1.0 / rhs[br]
187 }
188 }
189 };
190
191 let same_source =
192 matches!(output, OutputVar::Current { vsrc } if vsrc.eq_ignore_ascii_case(&insrc));
193 let output_impedance = if same_source {
194 input_impedance
197 } else {
198 rhs.fill(0.0);
199 match out {
200 OutRef::Volt(p, q) => {
201 rhs[p] -= 1.0;
202 rhs[q] += 1.0;
203 }
204 OutRef::Branch(b) => rhs[b] += 1.0,
205 }
206 engine.solve_factored(&mut rhs)?;
207 match out {
208 OutRef::Volt(p, q) => rhs[q] - rhs[p],
209 OutRef::Branch(b) => 1.0 / f64::max(1e-20, rhs[b]),
210 }
211 };
212
213 let out_name = match output {
214 OutputVar::Voltage { node, reference } => match reference {
215 Some(r) => format!("v(output_impedance_at_V({node},{r}))"),
216 None => format!("v(output_impedance_at_V({node}))"),
217 },
218 OutputVar::Current { vsrc } => format!("v({vsrc}#Output_impedance)"),
219 };
220 let variables = vec![
221 ("v(Transfer_function)".to_string(), VarType::Voltage),
222 (format!("v({insrc}#Input_impedance)"), VarType::Voltage),
223 (out_name, VarType::Voltage),
224 ];
225 Ok(Tf {
226 solution: Solution {
227 plotname: "Transfer Function".to_string(),
228 variables,
229 data: Data::Real(vec![
230 vec![transfer_function],
231 vec![input_impedance],
232 vec![output_impedance],
233 ]),
234 scale: None,
235 },
236 transfer_function,
237 input_impedance,
238 output_impedance,
239 notes,
240 })
241 }
242}