1use crate::compiler::component::{
5 Component, ComponentEdge, ControlParam, ControlParamKind, EdgeKind, GraphRole, PinConfig,
6 PinDirection, StampContext, StampResult, OPEN_CIRCUIT_R,
7};
8use crate::compiler::dyn_node::DynNode;
9use crate::compiler::validate::Severity;
10use crate::dsl::{CapConfig, PotTaper};
11use crate::tree::MnaSystem;
12
13use super::impl_component_dyn;
14
15#[derive(Debug, Clone, PartialEq)]
20pub struct Resistor {
21 pub value: f64,
22}
23
24impl Component for Resistor {
25 impl_component_dyn!();
26
27 fn type_tag(&self) -> &'static str {
28 "resistor"
29 }
30
31 fn is_passive(&self) -> bool {
32 true
33 }
34
35 fn pin_config(&self) -> PinConfig {
36 PinConfig {
37 valid_pins: &["a", "b"],
38 aliases: &[],
39 }
40 }
41
42 fn graph_role(&self) -> GraphRole {
43 GraphRole::Edge {
44 pin_a: "a",
45 pin_b: "b",
46 }
47 }
48
49 fn stamp_mna(
50 &self,
51 _comp_id: &str,
52 n1: Option<usize>,
53 n2: Option<usize>,
54 mna: &mut MnaSystem,
55 _sample_rate: f64,
56 ) -> StampResult {
57 mna.stamp_resistor(n1, n2, self.value as crate::Wave);
58 StampResult::Stamped
59 }
60
61 fn make_leaf(&self, comp_id: &str, _sample_rate: f64) -> Option<DynNode> {
62 if self.value.is_infinite() {
63 Some(DynNode::Resistor(Some(comp_id.to_string()), OPEN_CIRCUIT_R as crate::Wave))
64 } else {
65 Some(DynNode::Resistor(Some(comp_id.to_string()), self.value as crate::Wave))
66 }
67 }
68
69 fn edges(&self) -> Vec<ComponentEdge> {
70 vec![ComponentEdge {
71 pin_a: "a",
72 pin_b: "b",
73 kind: EdgeKind::Linear,
74 port_group: None,
75 }]
76 }
77
78 fn resistance(&self) -> Option<f64> {
79 Some(self.value)
80 }
81
82 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
83 let mut w = Vec::new();
84 if self.value <= 0.0 {
85 w.push((
86 Severity::Error,
87 format!(
88 "Resistor '{}' has non-positive value {:.2} \u{2126}",
89 comp_id, self.value
90 ),
91 ));
92 } else if self.value < 1.0 {
93 w.push((
94 Severity::Info,
95 format!(
96 "Resistor '{}' is {:.2} \u{2126} \u{2014} extremely low, did you mean {}k?",
97 comp_id, self.value, self.value
98 ),
99 ));
100 } else if self.value > 100e6 {
101 w.push((
102 Severity::Info,
103 format!(
104 "Resistor '{}' is {:.1} M\u{2126} \u{2014} unusually high",
105 comp_id,
106 self.value / 1e6
107 ),
108 ));
109 }
110 w
111 }
112
113 fn footprint_ref(&self) -> (&'static str, &'static str) {
114 ("Device:R", "R")
115 }
116
117 fn symbol_name(&self) -> &'static str {
118 "resistor"
119 }
120 fn layout_class(&self) -> &'static str {
121 "resistor"
122 }
123 fn display_value(&self) -> Option<String> {
124 Some(crate::kicad::format_eng(self.value, "Ω"))
125 }
126}
127
128#[derive(Debug, Clone, PartialEq)]
133pub struct Capacitor {
134 pub config: CapConfig,
135}
136
137impl Component for Capacitor {
138 impl_component_dyn!();
139
140 fn type_tag(&self) -> &'static str {
141 "capacitor"
142 }
143
144 fn is_passive(&self) -> bool {
145 true
146 }
147
148 fn pin_config(&self) -> PinConfig {
149 PinConfig {
150 valid_pins: &["a", "b"],
151 aliases: &[],
152 }
153 }
154
155 fn graph_role(&self) -> GraphRole {
156 GraphRole::Edge {
157 pin_a: "a",
158 pin_b: "b",
159 }
160 }
161
162 fn stamp_mna(
163 &self,
164 comp_id: &str,
165 _n1: Option<usize>,
166 _n2: Option<usize>,
167 _mna: &mut MnaSystem,
168 sample_rate: f64,
169 ) -> StampResult {
170 let rp = 1.0 / (2.0 * sample_rate * self.config.value);
171 StampResult::Reactive {
172 dyn_node: DynNode::Capacitor(Some(comp_id.to_string()), self.config.value as crate::Wave, rp as crate::Wave),
173 rp,
174 }
175 }
176
177 fn make_leaf(&self, comp_id: &str, sample_rate: f64) -> Option<DynNode> {
178 if self.config.leakage.is_some() || self.config.da.is_some() {
179 let rp = 1.0 / (2.0 * sample_rate * self.config.value);
180 let leakage_decay = if let Some(r_leak) = self.config.leakage {
181 let tau = r_leak * self.config.value;
182 let dt = 1.0 / sample_rate;
183 (-dt / tau).exp()
184 } else {
185 1.0
186 };
187 const DA_TIME_CONSTANT: f64 = 0.5;
188 let da_rate = if self.config.da.is_some() {
189 1.0 / (sample_rate * DA_TIME_CONSTANT)
190 } else {
191 0.0
192 };
193 Some(DynNode::LeakyCapacitor(
194 Some(comp_id.to_string()),
195 self.config.value as crate::Wave,
196 rp as crate::Wave,
197 leakage_decay as crate::Wave,
198 self.config.da.map(|v| v as crate::Wave),
199 0.0,
200 da_rate as crate::Wave,
201 ))
202 } else {
203 Some(DynNode::Capacitor(
204 Some(comp_id.to_string()),
205 self.config.value as crate::Wave,
206 (1.0 / (2.0 * sample_rate * self.config.value)) as crate::Wave,
207 ))
208 }
209 }
210
211 fn edges(&self) -> Vec<ComponentEdge> {
212 vec![ComponentEdge {
213 pin_a: "a",
214 pin_b: "b",
215 kind: EdgeKind::Reactive,
216 port_group: None,
217 }]
218 }
219
220 fn capacitance(&self) -> Option<f64> {
221 Some(self.config.value)
222 }
223
224 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
225 let mut w = Vec::new();
226 if self.config.value <= 0.0 {
227 w.push((
228 Severity::Error,
229 format!(
230 "Capacitor '{}' has non-positive value {:.2e} F",
231 comp_id, self.config.value
232 ),
233 ));
234 } else if self.config.value > 1.0 {
235 w.push((
236 Severity::Warning,
237 format!(
238 "Capacitor '{}' is {:.2} F \u{2014} over 1 farad, did you forget a unit suffix (n, u, p)?",
239 comp_id, self.config.value
240 ),
241 ));
242 }
243 if let Some(leak) = self.config.leakage {
244 if leak <= 0.0 {
245 w.push((
246 Severity::Error,
247 format!(
248 "Capacitor '{}' has non-positive leakage resistance {:.2} \u{2126}",
249 comp_id, leak
250 ),
251 ));
252 }
253 }
254 if let Some(da) = self.config.da {
255 if !(0.0..=1.0).contains(&da) {
256 w.push((
257 Severity::Warning,
258 format!(
259 "Capacitor '{}' has dielectric absorption {:.3} \u{2014} should be 0.0 to 1.0",
260 comp_id, da
261 ),
262 ));
263 }
264 }
265 w
266 }
267
268 fn footprint_ref(&self) -> (&'static str, &'static str) {
269 ("Device:C", "C")
270 }
271
272 fn symbol_name(&self) -> &'static str {
273 "capacitor"
274 }
275 fn layout_class(&self) -> &'static str {
276 "capacitor"
277 }
278 fn display_value(&self) -> Option<String> {
279 Some(crate::kicad::format_eng(self.config.value, "F"))
280 }
281}
282
283#[derive(Debug, Clone, PartialEq)]
288pub struct Inductor {
289 pub value: f64,
290}
291
292impl Component for Inductor {
293 impl_component_dyn!();
294
295 fn type_tag(&self) -> &'static str {
296 "inductor"
297 }
298
299 fn is_passive(&self) -> bool {
300 true
301 }
302
303 fn pin_config(&self) -> PinConfig {
304 PinConfig {
305 valid_pins: &["a", "b"],
306 aliases: &[],
307 }
308 }
309
310 fn graph_role(&self) -> GraphRole {
311 GraphRole::Edge {
312 pin_a: "a",
313 pin_b: "b",
314 }
315 }
316
317 fn stamp_mna(
318 &self,
319 comp_id: &str,
320 _n1: Option<usize>,
321 _n2: Option<usize>,
322 _mna: &mut MnaSystem,
323 sample_rate: f64,
324 ) -> StampResult {
325 let rp = 2.0 * sample_rate * self.value;
326 StampResult::Reactive {
327 dyn_node: DynNode::Inductor(Some(comp_id.to_string()), self.value as crate::Wave, rp as crate::Wave),
328 rp,
329 }
330 }
331
332 fn make_leaf(&self, comp_id: &str, sample_rate: f64) -> Option<DynNode> {
333 Some(DynNode::Inductor(
334 Some(comp_id.to_string()),
335 self.value as crate::Wave,
336 (2.0 * sample_rate * self.value) as crate::Wave,
337 ))
338 }
339
340 fn edges(&self) -> Vec<ComponentEdge> {
341 vec![ComponentEdge {
342 pin_a: "a",
343 pin_b: "b",
344 kind: EdgeKind::Reactive,
345 port_group: None,
346 }]
347 }
348
349 fn inductance(&self) -> Option<f64> {
350 Some(self.value)
351 }
352
353 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
354 let mut w = Vec::new();
355 if self.value <= 0.0 {
356 w.push((
357 Severity::Error,
358 format!(
359 "Inductor '{}' has non-positive value {:.2e} H",
360 comp_id, self.value
361 ),
362 ));
363 }
364 w
365 }
366
367 fn footprint_ref(&self) -> (&'static str, &'static str) {
368 ("Device:L", "L")
369 }
370
371 fn symbol_name(&self) -> &'static str {
372 "inductor"
373 }
374 fn layout_class(&self) -> &'static str {
375 "inductor"
376 }
377 fn display_value(&self) -> Option<String> {
378 Some(crate::kicad::format_eng(self.value, "H"))
379 }
380}
381
382#[derive(Debug, Clone, PartialEq)]
387pub struct Potentiometer {
388 pub max_r: f64,
389 pub taper: PotTaper,
390}
391
392impl Component for Potentiometer {
393 impl_component_dyn!();
394
395 fn type_tag(&self) -> &'static str {
396 "potentiometer"
397 }
398
399 fn ports(&self) -> Vec<(&'static str, &'static str)> {
400 vec![("a", "w"), ("w", "b")]
403 }
404
405 fn is_passive(&self) -> bool {
406 true
407 }
408
409 fn pin_config(&self) -> PinConfig {
410 PinConfig {
411 valid_pins: &["a", "b", "w", "wiper", "position"],
412 aliases: &[],
413 }
414 }
415
416 fn graph_role(&self) -> GraphRole {
417 GraphRole::Pot
418 }
419
420 fn stamp_mna(
421 &self,
422 comp_id: &str,
423 n1: Option<usize>,
424 n2: Option<usize>,
425 mna: &mut MnaSystem,
426 _sample_rate: f64,
427 ) -> StampResult {
428 let initial_pos = 0.5;
429 let tapered_pos = self.taper.apply(initial_pos);
430 let r = (tapered_pos * self.max_r as crate::Wave).max(1.0);
431 mna.stamp_resistor(n1, n2, r);
432 StampResult::Pot {
433 dyn_node: DynNode::Pot(comp_id.to_string(), self.max_r as crate::Wave, initial_pos, self.taper),
434 initial_conductance: 1.0 / r as f64,
435 }
436 }
437
438 fn stamp_mna_multi(
439 &self,
440 _comp_id: &str,
441 ctx: &mut StampContext,
442 mna: &mut MnaSystem,
443 ) -> StampResult {
444 let initial_pos = 0.5;
445 let tapered_pos = self.taper.apply(initial_pos);
446 let r_aw = (tapered_pos * self.max_r as crate::Wave).max(1.0);
448 let n_a = (ctx.pin_to_mna)("a");
449 let n_w = (ctx.pin_to_mna)("w").or_else(|| (ctx.pin_to_mna)("wiper"));
450 mna.stamp_resistor(n_a, n_w, r_aw);
451
452 let r_wb = ((1.0 - tapered_pos) * self.max_r as crate::Wave).max(1.0);
454 let n_b = (ctx.pin_to_mna)("b");
455 mna.stamp_resistor(n_w, n_b, r_wb);
456
457 StampResult::Stamped
458 }
459
460 fn make_leaf(&self, comp_id: &str, _sample_rate: f64) -> Option<DynNode> {
461 let initial_pos = 0.5;
462 let tapered_pos = self.taper.apply(initial_pos);
463 Some(DynNode::Pot(
464 comp_id.to_string(),
465 self.max_r as crate::Wave,
466 initial_pos,
467 self.taper,
468 ))
469 }
470
471 fn is_variable(&self) -> bool {
472 true
473 }
474
475 fn edges(&self) -> Vec<ComponentEdge> {
476 vec![ComponentEdge {
477 pin_a: "a",
478 pin_b: "b",
479 kind: EdgeKind::Linear,
480 port_group: None,
481 }]
482 }
483
484 fn controls(&self) -> Vec<ControlParam> {
485 vec![ControlParam {
486 name: "position",
487 kind: ControlParamKind::PotPosition,
488 }]
489 }
490
491 fn resistance(&self) -> Option<f64> {
492 Some(self.max_r)
493 }
494
495 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
496 let mut w = Vec::new();
497 if self.max_r <= 0.0 {
498 w.push((
499 Severity::Error,
500 format!(
501 "Potentiometer '{}' has non-positive max resistance {:.2} \u{2126}",
502 comp_id, self.max_r
503 ),
504 ));
505 }
506 w
507 }
508
509 fn footprint_ref(&self) -> (&'static str, &'static str) {
510 ("Device:R_Potentiometer", "RV")
511 }
512
513 fn symbol_name(&self) -> &'static str {
514 "pot"
515 }
516 fn layout_class(&self) -> &'static str {
517 "pot"
518 }
519 fn display_value(&self) -> Option<String> {
520 Some(crate::kicad::format_eng(self.max_r, "\u{2126}"))
521 }
522
523 fn signal_adjacencies(&self) -> Vec<(&'static str, &'static str)> {
524 vec![
525 ("a", "b"),
526 ("a", "wiper"),
527 ("wiper", "b"),
528 ("a", "w"),
529 ("w", "b"),
530 ]
531 }
532
533 fn pin_direction(&self, pin: &str) -> PinDirection {
534 match pin {
535 "a" => PinDirection::Input,
536 "wiper" | "b" | "w" => PinDirection::Output,
537 _ => PinDirection::Bidirectional,
538 }
539 }
540
541 fn is_pot(&self) -> bool {
542 true
543 }
544 fn pot_taper(&self) -> Option<crate::dsl::PotTaper> {
545 Some(self.taper)
546 }
547}
548
549#[derive(Debug, Clone, PartialEq)]
554pub struct Tempco {
555 pub resistance: f64,
556 pub ppm: f64,
557}
558
559impl Component for Tempco {
560 impl_component_dyn!();
561
562 fn type_tag(&self) -> &'static str {
563 "tempco resistor"
564 }
565
566 fn is_passive(&self) -> bool {
567 true
568 }
569
570 fn pin_config(&self) -> PinConfig {
571 PinConfig {
572 valid_pins: &["a", "b"],
573 aliases: &[],
574 }
575 }
576
577 fn graph_role(&self) -> GraphRole {
578 GraphRole::Edge {
579 pin_a: "a",
580 pin_b: "b",
581 }
582 }
583
584 fn stamp_mna(
585 &self,
586 _comp_id: &str,
587 n1: Option<usize>,
588 n2: Option<usize>,
589 mna: &mut MnaSystem,
590 _sample_rate: f64,
591 ) -> StampResult {
592 mna.stamp_resistor(n1, n2, self.resistance as crate::Wave);
593 StampResult::Stamped
594 }
595
596 fn make_leaf(&self, comp_id: &str, _sample_rate: f64) -> Option<DynNode> {
597 Some(DynNode::Resistor(
598 Some(comp_id.to_string()),
599 self.resistance as crate::Wave,
600 ))
601 }
602
603 fn edges(&self) -> Vec<ComponentEdge> {
604 vec![ComponentEdge {
605 pin_a: "a",
606 pin_b: "b",
607 kind: EdgeKind::Linear,
608 port_group: None,
609 }]
610 }
611
612 fn resistance(&self) -> Option<f64> {
613 Some(self.resistance)
614 }
615
616 fn footprint_ref(&self) -> (&'static str, &'static str) {
617 ("Device:R", "RT")
618 }
619
620 fn symbol_name(&self) -> &'static str {
621 "resistor"
622 }
623 fn layout_class(&self) -> &'static str {
624 "tempco"
625 }
626 fn display_value(&self) -> Option<String> {
627 None
628 }
629}
630
631#[derive(Debug, Clone, PartialEq)]
636pub struct CapSwitched {
637 pub values: Vec<f64>,
638}
639
640impl Component for CapSwitched {
641 impl_component_dyn!();
642
643 fn type_tag(&self) -> &'static str {
644 "switched capacitor"
645 }
646
647 fn is_passive(&self) -> bool {
648 true
649 }
650
651 fn pin_config(&self) -> PinConfig {
652 PinConfig {
653 valid_pins: &["a", "b"],
654 aliases: &[],
655 }
656 }
657
658 fn graph_role(&self) -> GraphRole {
659 GraphRole::Edge {
660 pin_a: "a",
661 pin_b: "b",
662 }
663 }
664
665 fn stamp_mna(
666 &self,
667 comp_id: &str,
668 _n1: Option<usize>,
669 _n2: Option<usize>,
670 _mna: &mut MnaSystem,
671 sample_rate: f64,
672 ) -> StampResult {
673 if let Some(&c) = self.values.first() {
674 if c.is_finite() && c > 0.0 {
675 let rp = 1.0 / (2.0 * sample_rate * c);
676 return StampResult::Reactive {
677 dyn_node: DynNode::Capacitor(Some(comp_id.to_string()), c as crate::Wave, rp as crate::Wave),
678 rp,
679 };
680 }
681 }
682 StampResult::Skip
683 }
684
685 fn make_leaf(&self, comp_id: &str, sample_rate: f64) -> Option<DynNode> {
686 let &c = self.values.first()?;
689 if c.is_finite() && c > 0.0 {
690 Some(DynNode::Capacitor(
691 Some(comp_id.to_string()),
692 c as crate::Wave,
693 (1.0 / (2.0 * sample_rate * c)) as crate::Wave,
694 ))
695 } else {
696 None
697 }
698 }
699
700 fn edges(&self) -> Vec<ComponentEdge> {
701 vec![ComponentEdge {
702 pin_a: "a",
703 pin_b: "b",
704 kind: EdgeKind::Reactive,
705 port_group: None,
706 }]
707 }
708
709 fn capacitance(&self) -> Option<f64> {
710 self.values.first().copied()
711 }
712
713 fn footprint_ref(&self) -> (&'static str, &'static str) {
714 ("Device:C", "C")
715 }
716
717 fn symbol_name(&self) -> &'static str {
718 "capacitor"
719 }
720 fn layout_class(&self) -> &'static str {
721 "cap_switched"
722 }
723}
724
725#[derive(Debug, Clone, PartialEq)]
730pub struct InductorSwitched {
731 pub values: Vec<f64>,
732}
733
734impl Component for InductorSwitched {
735 impl_component_dyn!();
736
737 fn type_tag(&self) -> &'static str {
738 "switched inductor"
739 }
740
741 fn is_passive(&self) -> bool {
742 true
743 }
744
745 fn pin_config(&self) -> PinConfig {
746 PinConfig {
747 valid_pins: &["a", "b"],
748 aliases: &[],
749 }
750 }
751
752 fn graph_role(&self) -> GraphRole {
753 GraphRole::Edge {
754 pin_a: "a",
755 pin_b: "b",
756 }
757 }
758
759 fn stamp_mna(
760 &self,
761 comp_id: &str,
762 _n1: Option<usize>,
763 _n2: Option<usize>,
764 _mna: &mut MnaSystem,
765 sample_rate: f64,
766 ) -> StampResult {
767 if let Some(&l) = self.values.first() {
768 if l.is_finite() && l > 0.0 {
769 let rp = 2.0 * sample_rate * l;
770 return StampResult::Reactive {
771 dyn_node: DynNode::Inductor(Some(comp_id.to_string()), l as crate::Wave, rp as crate::Wave),
772 rp,
773 };
774 }
775 }
776 StampResult::Skip
777 }
778
779 fn make_leaf(&self, comp_id: &str, sample_rate: f64) -> Option<DynNode> {
780 let &l = self.values.first()?;
783 if l.is_finite() && l > 0.0 {
784 Some(DynNode::Inductor(
785 Some(comp_id.to_string()),
786 l as crate::Wave,
787 (2.0 * sample_rate * l) as crate::Wave,
788 ))
789 } else {
790 None
791 }
792 }
793
794 fn edges(&self) -> Vec<ComponentEdge> {
795 vec![ComponentEdge {
796 pin_a: "a",
797 pin_b: "b",
798 kind: EdgeKind::Reactive,
799 port_group: None,
800 }]
801 }
802
803 fn inductance(&self) -> Option<f64> {
804 self.values.first().copied()
805 }
806
807 fn footprint_ref(&self) -> (&'static str, &'static str) {
808 ("Device:L", "L")
809 }
810
811 fn symbol_name(&self) -> &'static str {
812 "inductor"
813 }
814 fn layout_class(&self) -> &'static str {
815 "inductor_switched"
816 }
817}
818
819#[derive(Debug, Clone, PartialEq)]
824pub struct ResistorSwitched {
825 pub values: Vec<f64>,
826}
827
828impl Component for ResistorSwitched {
829 impl_component_dyn!();
830
831 fn type_tag(&self) -> &'static str {
832 "switched resistor"
833 }
834
835 fn is_passive(&self) -> bool {
836 true
837 }
838
839 fn pin_config(&self) -> PinConfig {
840 PinConfig {
841 valid_pins: &["a", "b"],
842 aliases: &[],
843 }
844 }
845
846 fn graph_role(&self) -> GraphRole {
847 GraphRole::Edge {
848 pin_a: "a",
849 pin_b: "b",
850 }
851 }
852
853 fn stamp_mna(
854 &self,
855 _comp_id: &str,
856 n1: Option<usize>,
857 n2: Option<usize>,
858 mna: &mut MnaSystem,
859 _sample_rate: f64,
860 ) -> StampResult {
861 if let Some(&r) = self.values.first() {
862 if r.is_finite() && r > 0.0 {
863 mna.stamp_resistor(n1, n2, r as crate::Wave);
864 }
865 }
866 StampResult::Stamped
867 }
868
869 fn make_leaf(&self, comp_id: &str, _sample_rate: f64) -> Option<DynNode> {
870 let &r = self.values.first()?;
874 if r.is_infinite() {
875 Some(DynNode::Resistor(Some(comp_id.to_string()), OPEN_CIRCUIT_R as crate::Wave))
876 } else if r.is_finite() && r > 0.0 {
877 Some(DynNode::Resistor(Some(comp_id.to_string()), r as crate::Wave))
878 } else {
879 None
880 }
881 }
882
883 fn edges(&self) -> Vec<ComponentEdge> {
884 vec![ComponentEdge {
885 pin_a: "a",
886 pin_b: "b",
887 kind: EdgeKind::Linear,
888 port_group: None,
889 }]
890 }
891
892 fn resistance(&self) -> Option<f64> {
893 self.values.first().copied()
894 }
895
896 fn footprint_ref(&self) -> (&'static str, &'static str) {
897 ("Device:R", "R")
898 }
899
900 fn symbol_name(&self) -> &'static str {
901 "resistor"
902 }
903 fn layout_class(&self) -> &'static str {
904 "resistor_switched"
905 }
906}