sindr_devices/varactor.rs
1//! Varactor diode model: voltage-dependent junction capacitance.
2//!
3//! A varactor is a reverse-biased diode whose junction capacitance varies with
4//! applied voltage. In DC analysis it is an open circuit. In transient analysis
5//! the capacitance is evaluated at the previous timestep voltage (freeze-and-stamp).
6
7/// Varactor diode parameters.
8#[derive(Debug, Clone)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct VaractorParams {
11 /// Zero-bias junction capacitance (F). Typical: 10e-12 (10 pF).
12 pub cj0: f64,
13 /// Built-in junction potential (V). Typical: 0.7 for Si.
14 pub phi: f64,
15 /// Grading coefficient (dimensionless). Typical: 0.5 (abrupt junction).
16 pub m: f64,
17}
18
19impl Default for VaractorParams {
20 fn default() -> Self {
21 Self {
22 cj0: 10e-12,
23 phi: 0.7,
24 m: 0.5,
25 }
26 }
27}
28
29/// Compute junction capacitance at voltage `v`.
30///
31/// C_j(V) = C_j0 / (1 - V/phi)^m
32///
33/// Clamped: V must not reach phi (singularity). Clamp V to 0.9 * phi max.
34/// For reverse bias (V < 0), capacitance increases correctly.
35/// For forward bias (V approaching phi), capacitance would diverge — clamp.
36pub fn junction_capacitance(v: f64, params: &VaractorParams) -> f64 {
37 let v_clamped = v.min(0.9 * params.phi);
38 let denom = (1.0 - v_clamped / params.phi).powf(params.m);
39 params.cj0 / denom.max(1e-6) // prevent division by near-zero
40}
41
42/// Compute the transient companion model for the varactor at `v_prev`.
43///
44/// Returns `(g_eq, i_eq)` where:
45/// - g_eq = C_j(v_prev) / dt (backward Euler capacitor conductance)
46/// - i_eq = -g_eq * v_prev (capacitor history current — note sign convention)
47///
48/// In DC analysis, stamp as open circuit: return (0.0, 0.0).
49/// Caller passes dt = 0.0 to signal DC, returning open circuit.
50pub fn varactor_companion(v_prev: f64, dt: f64, params: &VaractorParams) -> (f64, f64) {
51 if dt <= 0.0 {
52 // DC analysis: varactor is open circuit
53 return (0.0, 0.0);
54 }
55 let cj = junction_capacitance(v_prev, params);
56 let g_eq = cj / dt;
57 let i_eq = -g_eq * v_prev; // history current: I_eq = -C/dt * V_prev
58 (g_eq, i_eq)
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn varactor_dc_is_open_circuit() {
67 let params = VaractorParams::default();
68 let (g_eq, i_eq) = varactor_companion(0.0, 0.0, ¶ms);
69 assert_eq!(g_eq, 0.0);
70 assert_eq!(i_eq, 0.0);
71 }
72
73 #[test]
74 fn varactor_dc_is_open_circuit_at_nonzero_voltage() {
75 let params = VaractorParams::default();
76 let (g_eq, i_eq) = varactor_companion(-5.0, 0.0, ¶ms);
77 assert_eq!(g_eq, 0.0);
78 assert_eq!(i_eq, 0.0);
79 }
80
81 #[test]
82 fn varactor_capacitance_decreases_at_reverse_bias() {
83 // At 0V bias, C_j = cj0. At reverse bias (V < 0), (1 - V/phi) > 1,
84 // so denominator > 1, meaning C_j(V<0) < C_j(0) for the standard model.
85 // The varactor capacitance increases toward 0V from reverse bias.
86 let params = VaractorParams::default();
87 let c_at_zero = junction_capacitance(0.0, ¶ms);
88 let c_at_reverse = junction_capacitance(-2.0, ¶ms);
89 // At 0V: denom = 1.0; at -2V: denom = (1 + 2/0.7)^0.5 > 1, so C(-2) < C(0)
90 assert!(
91 c_at_zero > c_at_reverse,
92 "C(0V)={} should be > C(-2V)={}",
93 c_at_zero,
94 c_at_reverse
95 );
96 }
97
98 #[test]
99 fn varactor_transient_g_eq_is_cj_over_dt() {
100 let params = VaractorParams::default();
101 let v_prev = 0.0;
102 let dt = 1e-6;
103 let (g_eq, i_eq) = varactor_companion(v_prev, dt, ¶ms);
104 // At v=0: C_j = cj0 / 1.0 = cj0
105 let expected_g = params.cj0 / dt;
106 assert!(
107 (g_eq - expected_g).abs() < 1e-20,
108 "g_eq={} expected {}",
109 g_eq,
110 expected_g
111 );
112 // i_eq = -g_eq * v_prev = 0 at v=0
113 assert_eq!(i_eq, 0.0);
114 }
115
116 #[test]
117 fn varactor_capacitance_clamped_near_phi() {
118 let params = VaractorParams::default();
119 // Forward bias approaching phi should clamp and return finite value > cj0
120 let c_clamped = junction_capacitance(0.65, ¶ms);
121 assert!(c_clamped.is_finite(), "capacitance should be finite");
122 assert!(
123 c_clamped > params.cj0,
124 "forward-biased C={} should be > cj0={}",
125 c_clamped,
126 params.cj0
127 );
128 }
129}