1use hashbrown::HashMap;
4
5use crate::compiler::classify::NonlinearKind;
6use crate::compiler::component::{
7 Component, ComponentEdge, EdgeKind, GraphRole, KMethodSpec, ModulationSink, ModulationSinkKind,
8 NeighborReq, NeighborRole, PinConfig, PinDirection, SignalTerminals, StampResult,
9 K_AXIS_INCIDENT_CONTROL_2D, K_AXIS_INCIDENT_CONTROL_CONTROL_3D,
10};
11use crate::compiler::graph::NodeId;
12use crate::tree::MnaSystem;
13
14use super::impl_component_dyn;
15
16#[derive(Debug, Clone, PartialEq)]
21pub struct Triode {
22 pub model: String,
23}
24
25impl Component for Triode {
26 impl_component_dyn!();
27
28 fn type_tag(&self) -> &'static str {
29 "triode"
30 }
31
32 fn ports(&self) -> Vec<(&'static str, &'static str)> {
33 vec![("plate", "cathode")] }
35
36 fn signal_terminals(&self) -> SignalTerminals {
37 SignalTerminals::Amplifier {
38 input: "grid",
39 output: "plate",
40 control: None,
41 }
42 }
43
44 fn is_passive(&self) -> bool {
45 false
46 }
47
48 fn is_nonlinear(&self) -> bool {
49 true
50 }
51
52 fn pin_config(&self) -> PinConfig {
53 PinConfig {
54 valid_pins: &["plate", "cathode", "grid", "vgk"],
55 aliases: &[],
56 }
57 }
58
59 fn modulation_pins(&self) -> &'static [&'static str] {
60 &["vgk"]
61 }
62
63 fn graph_role(&self) -> GraphRole {
64 GraphRole::CoupledEdge {
65 edge_pin_a: "plate",
66 edge_pin_b: "cathode",
67 coupled_pins: &["plate", "cathode", "grid"],
68 }
69 }
70
71 fn stamp_mna(
72 &self,
73 _comp_id: &str,
74 _n1: Option<usize>,
75 _n2: Option<usize>,
76 _mna: &mut MnaSystem,
77 _sample_rate: f64,
78 ) -> StampResult {
79 StampResult::Skip
80 }
81
82 fn edges(&self) -> Vec<ComponentEdge> {
83 vec![ComponentEdge {
84 pin_a: "plate",
85 pin_b: "cathode",
86 kind: EdgeKind::Nonlinear,
87 port_group: None,
88 }]
89 }
90
91 fn classify_nonlinear(
92 &self,
93 comp_id: &str,
94 node_a: NodeId,
95 node_b: NodeId,
96 _gnd_node: NodeId,
97 node_names: &HashMap<String, NodeId>,
98 ) -> Option<(NonlinearKind, Vec<NodeId>)> {
99 let grid_node = node_names.get(&format!("{}.grid", comp_id)).copied();
100 Some((
101 NonlinearKind::Triode {
102 model_name: self.model.clone(),
103 plate_node: node_a,
104 cathode_node: node_b,
105 grid_node,
106 parallel_count: 1,
107 is_vari_mu: false,
108 },
109 vec![node_a, node_b],
110 ))
111 }
112
113 fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
114 match pin {
115 "vgk" => Some(ModulationSink {
116 target_kind: ModulationSinkKind::TriodeVgk,
117 bias: -2.0,
118 range: 2.0,
119 }),
120 _ => None,
121 }
122 }
123
124 fn footprint_ref(&self) -> (&'static str, &'static str) {
125 ("Valve:Triode", "V")
126 }
127
128 fn symbol_name(&self) -> &'static str {
129 "triode"
130 }
131 fn layout_class(&self) -> &'static str {
132 "triode"
133 }
134
135 fn pin_direction(&self, pin: &str) -> PinDirection {
136 match pin {
137 "grid" => PinDirection::Input,
138 "plate" => PinDirection::Output,
139 "cathode" => PinDirection::Down,
140 _ => PinDirection::Bidirectional,
141 }
142 }
143
144 fn is_tube(&self) -> bool {
145 true
146 }
147 fn is_gain_device(&self) -> bool {
148 true
149 }
150 fn k_method_spec(&self) -> Option<KMethodSpec> {
151 Some(KMethodSpec {
152 axes: K_AXIS_INCIDENT_CONTROL_2D,
153 reason: "tube: 2D memoryless I-V",
154 })
155 }
156 fn model_name(&self) -> Option<&str> {
157 Some(&self.model)
158 }
159
160 fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
161 vec![
165 ("plate", vec![NeighborReq::required(NeighborRole::Load)]),
166 (
167 "cathode",
168 vec![
169 NeighborReq::required(NeighborRole::Load),
170 NeighborReq::optional(NeighborRole::Ref),
171 ],
172 ),
173 (
174 "grid",
175 vec![
176 NeighborReq::optional(NeighborRole::Ref),
177 NeighborReq::optional(NeighborRole::Signal),
178 ],
179 ),
180 ]
181 }
182}
183
184#[derive(Debug, Clone, PartialEq)]
189pub struct Pentode {
190 pub model: String,
191}
192
193impl Component for Pentode {
194 impl_component_dyn!();
195
196 fn type_tag(&self) -> &'static str {
197 "pentode"
198 }
199
200 fn ports(&self) -> Vec<(&'static str, &'static str)> {
201 vec![("plate", "cathode")]
202 }
203
204 fn signal_terminals(&self) -> SignalTerminals {
205 SignalTerminals::Amplifier {
206 input: "grid",
207 output: "plate",
208 control: None,
209 }
210 }
211
212 fn is_passive(&self) -> bool {
213 false
214 }
215
216 fn is_nonlinear(&self) -> bool {
217 true
218 }
219
220 fn pin_config(&self) -> PinConfig {
221 PinConfig {
222 valid_pins: &["plate", "cathode", "g1", "g2", "grid", "screen", "vg1k"],
223 aliases: &[],
224 }
225 }
226
227 fn modulation_pins(&self) -> &'static [&'static str] {
228 &["vg1k"]
229 }
230
231 fn graph_role(&self) -> GraphRole {
232 GraphRole::CoupledEdge {
233 edge_pin_a: "plate",
234 edge_pin_b: "cathode",
235 coupled_pins: &["plate", "cathode", "grid", "screen"],
236 }
237 }
238
239 fn stamp_mna(
240 &self,
241 _comp_id: &str,
242 _n1: Option<usize>,
243 _n2: Option<usize>,
244 _mna: &mut MnaSystem,
245 _sample_rate: f64,
246 ) -> StampResult {
247 StampResult::Skip
248 }
249
250 fn edges(&self) -> Vec<ComponentEdge> {
251 vec![ComponentEdge {
252 pin_a: "plate",
253 pin_b: "cathode",
254 kind: EdgeKind::Nonlinear,
255 port_group: None,
256 }]
257 }
258
259 fn classify_nonlinear(
260 &self,
261 comp_id: &str,
262 node_a: NodeId,
263 node_b: NodeId,
264 _gnd_node: NodeId,
265 node_names: &HashMap<String, NodeId>,
266 ) -> Option<(NonlinearKind, Vec<NodeId>)> {
267 let grid_node = node_names
268 .get(&format!("{comp_id}.grid",))
269 .or_else(|| node_names.get(&format!("{comp_id}.g1")))
270 .copied();
271 Some((
272 NonlinearKind::Pentode {
273 model_name: self.model.clone(),
274 plate_node: node_a,
275 cathode_node: node_b,
276 grid_node,
277 },
278 vec![node_a, node_b],
279 ))
280 }
281
282 fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
283 match pin {
284 "vg1k" => Some(ModulationSink {
285 target_kind: ModulationSinkKind::PentodeVg1k,
286 bias: -2.0,
287 range: 2.0,
288 }),
289 _ => None,
290 }
291 }
292
293 fn footprint_ref(&self) -> (&'static str, &'static str) {
294 ("Valve:Triode", "V")
295 }
296
297 fn symbol_name(&self) -> &'static str {
298 "pentode"
299 }
300 fn layout_class(&self) -> &'static str {
301 "pentode"
302 }
303
304 fn pin_direction(&self, pin: &str) -> PinDirection {
305 match pin {
306 "grid" => PinDirection::Input,
307 "plate" => PinDirection::Output,
308 "screen" => PinDirection::Up,
309 "cathode" => PinDirection::Down,
310 _ => PinDirection::Bidirectional,
311 }
312 }
313
314 fn is_tube(&self) -> bool {
315 true
316 }
317 fn is_gain_device(&self) -> bool {
318 true
319 }
320 fn k_method_spec(&self) -> Option<KMethodSpec> {
321 Some(KMethodSpec {
322 axes: K_AXIS_INCIDENT_CONTROL_2D,
323 reason: "tube: 2D memoryless I-V",
324 })
325 }
326 fn model_name(&self) -> Option<&str> {
327 Some(&self.model)
328 }
329
330 fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
331 vec![
332 ("plate", vec![NeighborReq::required(NeighborRole::Load)]),
333 (
334 "cathode",
335 vec![
336 NeighborReq::required(NeighborRole::Load),
337 NeighborReq::optional(NeighborRole::Ref),
338 ],
339 ),
340 (
341 "screen",
342 vec![NeighborReq::optional(NeighborRole::Ref)],
343 ),
344 (
345 "grid",
346 vec![
347 NeighborReq::optional(NeighborRole::Ref),
348 NeighborReq::optional(NeighborRole::Signal),
349 ],
350 ),
351 ]
352 }
353}
354
355#[derive(Debug, Clone, PartialEq)]
360pub struct VariMu {
361 pub model: String,
362}
363
364impl Component for VariMu {
365 impl_component_dyn!();
366
367 fn type_tag(&self) -> &'static str {
368 "variable-mu triode"
369 }
370
371 fn ports(&self) -> Vec<(&'static str, &'static str)> {
372 vec![("plate", "cathode")]
373 }
374
375 fn signal_terminals(&self) -> SignalTerminals {
376 SignalTerminals::Amplifier {
377 input: "grid",
378 output: "plate",
379 control: None,
380 }
381 }
382
383 fn is_passive(&self) -> bool {
384 false
385 }
386
387 fn is_nonlinear(&self) -> bool {
388 true
389 }
390
391 fn pin_config(&self) -> PinConfig {
392 PinConfig {
393 valid_pins: &["plate", "cathode", "grid", "vgk"],
394 aliases: &[],
395 }
396 }
397
398 fn modulation_pins(&self) -> &'static [&'static str] {
399 &["vgk"]
400 }
401
402 fn graph_role(&self) -> GraphRole {
403 GraphRole::CoupledEdge {
404 edge_pin_a: "plate",
405 edge_pin_b: "cathode",
406 coupled_pins: &["plate", "cathode", "grid"],
407 }
408 }
409
410 fn stamp_mna(
411 &self,
412 _comp_id: &str,
413 _n1: Option<usize>,
414 _n2: Option<usize>,
415 _mna: &mut MnaSystem,
416 _sample_rate: f64,
417 ) -> StampResult {
418 StampResult::Skip
419 }
420
421 fn edges(&self) -> Vec<ComponentEdge> {
422 vec![ComponentEdge {
423 pin_a: "plate",
424 pin_b: "cathode",
425 kind: EdgeKind::Nonlinear,
426 port_group: None,
427 }]
428 }
429
430 fn classify_nonlinear(
431 &self,
432 comp_id: &str,
433 node_a: NodeId,
434 node_b: NodeId,
435 _gnd_node: NodeId,
436 node_names: &HashMap<String, NodeId>,
437 ) -> Option<(NonlinearKind, Vec<NodeId>)> {
438 let grid_node = node_names.get(&format!("{}.grid", comp_id)).copied();
439 Some((
440 NonlinearKind::Triode {
441 model_name: self.model.clone(),
442 plate_node: node_a,
443 cathode_node: node_b,
444 grid_node,
445 parallel_count: 1,
446 is_vari_mu: true,
447 },
448 vec![node_a, node_b],
449 ))
450 }
451
452 fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
453 match pin {
454 "vgk" => Some(ModulationSink {
455 target_kind: ModulationSinkKind::VariMuVgk,
456 bias: -2.0,
457 range: 2.0,
458 }),
459 _ => None,
460 }
461 }
462
463 fn footprint_ref(&self) -> (&'static str, &'static str) {
464 ("Valve:Triode", "V")
465 }
466
467 fn symbol_name(&self) -> &'static str {
468 "triode"
469 }
470 fn layout_class(&self) -> &'static str {
471 "vari_mu"
472 }
473
474 fn pin_direction(&self, pin: &str) -> PinDirection {
475 match pin {
476 "grid" => PinDirection::Input,
477 "plate" => PinDirection::Output,
478 "cathode" => PinDirection::Down,
479 _ => PinDirection::Bidirectional,
480 }
481 }
482
483 fn is_tube(&self) -> bool {
484 true
485 }
486 fn is_gain_device(&self) -> bool {
487 true
488 }
489 fn k_method_spec(&self) -> Option<KMethodSpec> {
490 Some(KMethodSpec {
491 axes: K_AXIS_INCIDENT_CONTROL_CONTROL_3D,
492 reason: "pentode: 3D memoryless I-V (Vg1k, Vg2k, Vpk)",
493 })
494 }
495 fn model_name(&self) -> Option<&str> {
496 Some(&self.model)
497 }
498
499 fn terminal_requirements(&self) -> Vec<(&'static str, Vec<NeighborReq>)> {
500 vec![
503 ("plate", vec![NeighborReq::required(NeighborRole::Load)]),
504 (
505 "cathode",
506 vec![
507 NeighborReq::required(NeighborRole::Load),
508 NeighborReq::optional(NeighborRole::Ref),
509 ],
510 ),
511 (
512 "grid",
513 vec![
514 NeighborReq::optional(NeighborRole::Ref),
515 NeighborReq::optional(NeighborRole::Signal),
516 ],
517 ),
518 ]
519 }
520}