pedalkernel/compiler/components/
diodes.rs1use hashbrown::HashMap;
4
5use crate::compiler::classify::NonlinearKind;
6use crate::compiler::component::{
7 Component, ComponentEdge, EdgeKind, GraphRole, KMethodSpec, PinConfig, PinDirection,
8 SignalTerminals, SolverMethod, StampResult, K_AXIS_INCIDENT_1D,
9};
10use crate::compiler::graph::NodeId;
11use crate::compiler::validate::Severity;
12use crate::dsl::{DiodeType, NeonType};
13use crate::tree::MnaSystem;
14
15use super::impl_component_dyn;
16
17#[derive(Debug, Clone, PartialEq)]
22pub struct Diode {
23 pub diode_type: DiodeType,
24}
25
26impl Component for Diode {
27 impl_component_dyn!();
28
29 fn type_tag(&self) -> &'static str {
30 "diode"
31 }
32
33 fn signal_terminals(&self) -> SignalTerminals {
34 SignalTerminals::TwoPort {
35 input: "a",
36 output: "b",
37 }
38 }
39
40 fn is_passive(&self) -> bool {
41 false
42 }
43
44 fn is_nonlinear(&self) -> bool {
45 true
46 }
47
48 fn pin_config(&self) -> PinConfig {
49 PinConfig {
50 valid_pins: &["a", "b"],
51 aliases: &[],
52 }
53 }
54
55 fn graph_role(&self) -> GraphRole {
56 GraphRole::Edge {
57 pin_a: "a",
58 pin_b: "b",
59 }
60 }
61
62 fn stamp_mna(
63 &self,
64 _comp_id: &str,
65 _n1: Option<usize>,
66 _n2: Option<usize>,
67 _mna: &mut MnaSystem,
68 _sample_rate: f64,
69 ) -> StampResult {
70 StampResult::Skip
71 }
72
73 fn edges(&self) -> Vec<ComponentEdge> {
74 vec![ComponentEdge {
75 pin_a: "a",
76 pin_b: "b",
77 kind: EdgeKind::Nonlinear,
78 port_group: None,
79 }]
80 }
81
82 fn classify_nonlinear(
83 &self,
84 _comp_id: &str,
85 node_a: NodeId,
86 node_b: NodeId,
87 gnd_node: NodeId,
88 _node_names: &HashMap<String, NodeId>,
89 ) -> Option<(NonlinearKind, Vec<NodeId>)> {
90 let diode_jn = if node_b == gnd_node {
91 node_a
92 } else if node_a == gnd_node {
93 node_b
94 } else {
95 node_a
96 };
97 Some((NonlinearKind::SingleDiode(self.diode_type), vec![diode_jn]))
98 }
99
100 fn footprint_ref(&self) -> (&'static str, &'static str) {
101 ("Device:D", "D")
102 }
103
104 fn symbol_name(&self) -> &'static str {
105 "diode"
106 }
107 fn layout_class(&self) -> &'static str {
108 "diode"
109 }
110
111 fn pin_direction(&self, pin: &str) -> PinDirection {
112 match pin {
113 "a" => PinDirection::Input,
114 "b" => PinDirection::Output,
115 _ => PinDirection::Bidirectional,
116 }
117 }
118
119 fn solver_hint(&self) -> Option<SolverMethod> {
120 Some(SolverMethod::WrightOmega)
121 }
122
123 fn is_diode_family(&self) -> bool {
124 true
125 }
126 fn diode_type(&self) -> Option<DiodeType> {
127 Some(self.diode_type)
128 }
129 fn k_method_spec(&self) -> Option<KMethodSpec> {
130 Some(KMethodSpec {
131 axes: K_AXIS_INCIDENT_1D,
132 reason: "diode: 1D memoryless monotonic I-V",
133 })
134 }
135}
136
137#[derive(Debug, Clone, PartialEq)]
142pub struct DiodePair {
143 pub diode_type: DiodeType,
144}
145
146impl Component for DiodePair {
147 impl_component_dyn!();
148
149 fn type_tag(&self) -> &'static str {
150 "diode pair"
151 }
152
153 fn signal_terminals(&self) -> SignalTerminals {
154 SignalTerminals::TwoPort {
155 input: "a",
156 output: "b",
157 }
158 }
159
160 fn is_passive(&self) -> bool {
161 false
162 }
163
164 fn is_nonlinear(&self) -> bool {
165 true
166 }
167
168 fn pin_config(&self) -> PinConfig {
169 PinConfig {
170 valid_pins: &["a", "b"],
171 aliases: &[],
172 }
173 }
174
175 fn graph_role(&self) -> GraphRole {
176 GraphRole::Edge {
177 pin_a: "a",
178 pin_b: "b",
179 }
180 }
181
182 fn stamp_mna(
183 &self,
184 _comp_id: &str,
185 _n1: Option<usize>,
186 _n2: Option<usize>,
187 _mna: &mut MnaSystem,
188 _sample_rate: f64,
189 ) -> StampResult {
190 StampResult::Skip
191 }
192
193 fn edges(&self) -> Vec<ComponentEdge> {
194 vec![ComponentEdge {
195 pin_a: "a",
196 pin_b: "b",
197 kind: EdgeKind::Nonlinear,
198 port_group: None,
199 }]
200 }
201
202 fn classify_nonlinear(
203 &self,
204 _comp_id: &str,
205 node_a: NodeId,
206 node_b: NodeId,
207 gnd_node: NodeId,
208 _node_names: &HashMap<String, NodeId>,
209 ) -> Option<(NonlinearKind, Vec<NodeId>)> {
210 let diode_jn = if node_b == gnd_node {
211 node_a
212 } else if node_a == gnd_node {
213 node_b
214 } else {
215 node_a
216 };
217 Some((NonlinearKind::DiodePair(self.diode_type), vec![diode_jn]))
218 }
219
220 fn footprint_ref(&self) -> (&'static str, &'static str) {
221 ("Device:D", "D")
222 }
223
224 fn symbol_name(&self) -> &'static str {
225 "diode"
226 }
227 fn layout_class(&self) -> &'static str {
228 "diode_pair"
229 }
230
231 fn pin_direction(&self, pin: &str) -> PinDirection {
232 match pin {
233 "a" => PinDirection::Input,
234 "b" => PinDirection::Output,
235 _ => PinDirection::Bidirectional,
236 }
237 }
238
239 fn solver_hint(&self) -> Option<SolverMethod> {
240 Some(SolverMethod::WrightOmega)
241 }
242
243 fn is_diode_family(&self) -> bool {
244 true
245 }
246 fn diode_type(&self) -> Option<DiodeType> {
247 Some(self.diode_type)
248 }
249 fn k_method_spec(&self) -> Option<KMethodSpec> {
250 Some(KMethodSpec {
251 axes: K_AXIS_INCIDENT_1D,
252 reason: "diode pair: 1D memoryless symmetric I-V",
253 })
254 }
255}
256
257#[derive(Debug, Clone, PartialEq)]
262pub struct Zener {
263 pub breakdown_voltage: f64,
264}
265
266impl Component for Zener {
267 impl_component_dyn!();
268
269 fn type_tag(&self) -> &'static str {
270 "zener diode"
271 }
272
273 fn signal_terminals(&self) -> SignalTerminals {
274 SignalTerminals::TwoPort {
275 input: "a",
276 output: "b",
277 }
278 }
279
280 fn is_passive(&self) -> bool {
281 false
282 }
283
284 fn is_nonlinear(&self) -> bool {
285 true
286 }
287
288 fn pin_config(&self) -> PinConfig {
289 PinConfig {
290 valid_pins: &["a", "b"],
291 aliases: &[],
292 }
293 }
294
295 fn graph_role(&self) -> GraphRole {
296 GraphRole::Edge {
297 pin_a: "a",
298 pin_b: "b",
299 }
300 }
301
302 fn stamp_mna(
303 &self,
304 _comp_id: &str,
305 _n1: Option<usize>,
306 _n2: Option<usize>,
307 _mna: &mut MnaSystem,
308 _sample_rate: f64,
309 ) -> StampResult {
310 StampResult::Skip
311 }
312
313 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
314 let mut w = vec![];
315 if self.breakdown_voltage <= 0.0 {
316 w.push((
317 Severity::Error,
318 format!(
319 "Zener '{}' has non-positive breakdown voltage {:.2} V",
320 comp_id, self.breakdown_voltage
321 ),
322 ));
323 }
324 w
325 }
326
327 fn edges(&self) -> Vec<ComponentEdge> {
328 vec![ComponentEdge {
329 pin_a: "a",
330 pin_b: "b",
331 kind: EdgeKind::Nonlinear,
332 port_group: None,
333 }]
334 }
335
336 fn classify_nonlinear(
337 &self,
338 _comp_id: &str,
339 _node_a: NodeId,
340 node_b: NodeId,
341 gnd_node: NodeId,
342 _node_names: &HashMap<String, NodeId>,
343 ) -> Option<(NonlinearKind, Vec<NodeId>)> {
344 let jn = if node_b == gnd_node { _node_a } else { node_b };
345 Some((
346 NonlinearKind::Zener {
347 voltage: self.breakdown_voltage,
348 },
349 vec![jn],
350 ))
351 }
352
353 fn footprint_ref(&self) -> (&'static str, &'static str) {
354 ("Device:D_Zener", "D")
355 }
356
357 fn symbol_name(&self) -> &'static str {
358 "zener"
359 }
360 fn layout_class(&self) -> &'static str {
361 "zener"
362 }
363 fn display_value(&self) -> Option<String> {
364 Some(format!("{}V", self.breakdown_voltage))
365 }
366
367 fn pin_direction(&self, pin: &str) -> PinDirection {
368 match pin {
369 "a" => PinDirection::Input,
370 "b" => PinDirection::Output,
371 _ => PinDirection::Bidirectional,
372 }
373 }
374
375 fn is_diode_family(&self) -> bool {
376 true
377 }
378 fn k_method_spec(&self) -> Option<KMethodSpec> {
379 Some(KMethodSpec {
380 axes: K_AXIS_INCIDENT_1D,
381 reason: "zener: 1D memoryless I-V with reverse breakdown",
382 })
383 }
384}
385
386#[derive(Debug, Clone, PartialEq)]
391pub struct Neon {
392 pub neon_type: NeonType,
393}
394
395impl Component for Neon {
396 impl_component_dyn!();
397
398 fn type_tag(&self) -> &'static str {
399 "neon bulb"
400 }
401
402 fn signal_terminals(&self) -> SignalTerminals {
403 SignalTerminals::TwoPort {
404 input: "a",
405 output: "b",
406 }
407 }
408
409 fn is_passive(&self) -> bool {
410 false
411 }
412
413 fn is_nonlinear(&self) -> bool {
414 true
415 }
416
417 fn pin_config(&self) -> PinConfig {
418 PinConfig {
419 valid_pins: &["a", "b"],
420 aliases: &[],
421 }
422 }
423
424 fn graph_role(&self) -> GraphRole {
425 GraphRole::Edge {
426 pin_a: "a",
427 pin_b: "b",
428 }
429 }
430
431 fn stamp_mna(
432 &self,
433 _comp_id: &str,
434 _n1: Option<usize>,
435 _n2: Option<usize>,
436 _mna: &mut MnaSystem,
437 _sample_rate: f64,
438 ) -> StampResult {
439 StampResult::Skip
440 }
441
442 fn edges(&self) -> Vec<ComponentEdge> {
443 vec![ComponentEdge {
444 pin_a: "a",
445 pin_b: "b",
446 kind: EdgeKind::Nonlinear,
447 port_group: None,
448 }]
449 }
450
451 fn footprint_ref(&self) -> (&'static str, &'static str) {
452 match self.neon_type {
453 NeonType::Ne2 | NeonType::Ne51 | NeonType::Ne83 => ("Device:Lamp_Neon", "NE"),
454 }
455 }
456
457 fn symbol_name(&self) -> &'static str {
458 "neon"
459 }
460 fn layout_class(&self) -> &'static str {
461 "neon"
462 }
463 fn k_method_candidacy(&self) -> (bool, usize, &'static str) {
464 (
467 false,
468 1,
469 "neon: hysteretic I-V (strike/maintain) — not monotonic",
470 )
471 }
472}