1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// the phasor was always a geonum
//
// electrical engineering teaches complex impedance as a computational trick —
// "pretend the circuit is complex-valued, solve, take the real part" — and
// then spends a curriculum managing the pretense. there is no trick to manage:
// an impedance is a magnitude and an angle, a phasor is [mag, angle], and the
// circuit laws are geonum arithmetic
//
// - reactance sign is a grade: inductive current lags (quadrant I), the
// capacitive angle sits at grade 3 — above vs below resonance is a grade
// flip, not a ± on an imaginary part
// - resonance is interference: ωL and 1/ωC sit π apart and cancel by the
// opposite-angle branch of addition, leaving pure resistance
// - real and reactive power are the adj/opp split already in core: P = S·adj,
// Q = S·opp, and P² + Q² = S² is the quadrature closing
// - three-phase power exists because angles interfere: the balanced neutral
// carries nothing, and the delivered power is ripple-free because three
// second-harmonic ripples 2π/3 apart wave_sum to zero — constant torque,
// read off interference
// - power factor correction is angle arithmetic: inject the opp leg's
// opposite and the apparent power rotates home to grade 0
//
// run: cargo test --test phasor_test -- --show-output
use geonum::*;
use std::f64::consts::PI;
const R: f64 = 50.0; // Ω
const L: f64 = 0.1; // H
const C: f64 = 1e-5; // F — resonance at ω₀ = 1/√(LC) = 1000 rad/s
// series RLC impedance at frequency ω: three geonums summed
fn impedance(omega: f64) -> Geonum {
let z_l = Geonum::new_with_angle(omega * L, Angle::new(1.0, 2.0)); // ωL at π/2
let z_c = Geonum::new_with_angle(1.0 / (omega * C), Angle::new(3.0, 2.0)); // 1/ωC at 3π/2
(z_l + z_c) + Geonum::new(R, 0.0, 1.0)
}
#[test]
fn it_cancels_reactance_at_resonance() {
// at ω₀ the inductive and capacitive reactances are equal magnitudes π
// apart — they cancel by interference and the circuit is pure resistance
let z0 = impedance(1000.0);
assert!(
z0.near_mag(R),
"at resonance |Z| = R — the reactances cancelled"
);
assert_eq!(
z0.angle.grade(),
0,
"and the angle is home: pure resistance"
);
assert!(z0.angle.near_rem(0.0), "no residual phase");
// above resonance the inductor wins: net reactance +150 at ω = 2000
let z_high = impedance(2000.0);
assert!(
z_high.near_mag((R * R + 150.0 * 150.0).sqrt()),
"|Z| = √(50² + 150²)"
);
assert_eq!(z_high.angle.grade(), 0, "inductive: quadrant I");
assert!(
z_high.angle.near_rem(3.0_f64.atan()),
"phase = atan(150/50) — current lags"
);
// below resonance the capacitor wins: the reactance sign is a GRADE, not a
// minus on an imaginary part
let z_low = impedance(500.0);
assert_eq!(
z_low.angle.grade(),
3,
"capacitive: quadrant IV — the sign flip across resonance is a grade flip"
);
}
#[test]
fn it_splits_power_into_adj_and_opp() {
// drive the ω = 2000 circuit with 120 V. apparent power S rides the
// impedance angle; real and reactive power are its two quadrature legs —
// the adj/opp split already in core
let v = 120.0;
let z = impedance(2000.0);
// current magnitude by geonum division — the quotient carries the
// inversion's π (numbers_test lands quotients at grade 2); the magnitude
// is the reading
let i = Geonum::new(v, 0.0, 1.0) / z;
assert!(i.near_mag(v / z.mag), "|I| = |V|/|Z|");
let s = Geonum::new_with_angle(v * i.mag, z.angle); // apparent power at the impedance angle
let p = s.adj(); // real power — the aligned leg
let q = s.opp(); // reactive power — the quadrature leg
// anchored to dissipation the test never constructed: real power is what
// the resistor burns (the watts on the bill), reactive is what the net
// reactance circulates (the VArs the utility penalizes)
assert!((p.mag - i.mag * i.mag * R).abs() < 1e-9, "P = I²R");
assert!((q.mag - i.mag * i.mag * 150.0).abs() < 1e-9, "Q = I²X");
// the power triangle is the quadrature closing
assert!(
(p.mag * p.mag + q.mag * q.mag - s.mag * s.mag).abs() < 1e-6,
"P² + Q² = S²"
);
}
#[test]
fn it_cancels_the_neutral_by_interference() {
// three balanced phases 2π/3 apart: the neutral current is their wave_sum,
// and it vanishes because the angles interfere — not because a law says so
let balanced: GeoCollection = (0..3)
.map(|k| Geonum::new_with_angle(10.0, Angle::new(2.0 * k as f64, 3.0)))
.collect();
assert!(
balanced.wave_sum().near_mag(0.0),
"the balanced neutral carries nothing"
);
// unbalance one phase and the neutral carries exactly the imbalance — the
// extra 2 A of phase 3, pointing along phase 3
let unbalanced: GeoCollection = [10.0, 10.0, 12.0]
.iter()
.enumerate()
.map(|(k, &mag)| Geonum::new_with_angle(mag, Angle::new(2.0 * k as f64, 3.0)))
.collect();
let neutral = unbalanced.wave_sum();
assert!(neutral.near_mag(2.0), "the neutral reads the 2 A imbalance");
assert_eq!(
neutral.angle.base_angle(),
Angle::new(4.0, 3.0).base_angle(),
"pointing along the heavy phase"
);
}
#[test]
fn it_delivers_ripple_free_power_in_three_phase() {
// per-phase instantaneous power is VI·cos φ plus a double-frequency ripple.
// the three ripples sit 2π/3 apart and wave_sum to zero at every instant —
// the motor sees constant torque. this cancellation is why three-phase
// exists
let (v, i) = (120.0, 5.0);
let phi = 0.8_f64.acos(); // power factor 0.8
let omega = 2.0 * PI * 60.0;
for t in [0.0, 0.001, 0.004, 0.007, 0.011] {
// the three ripple terms as geonums at 2ωt − φ + k·2π/3
let ripples: GeoCollection = (0..3)
.map(|k| {
let angle =
Angle::new((2.0 * omega * t - phi) / PI, 1.0) + Angle::new(2.0 * k as f64, 3.0);
Geonum::new_with_angle(v * i, angle)
})
.collect();
assert!(
ripples.wave_sum().near_mag(0.0),
"t={t}: the second harmonics interfere to zero"
);
// foil: the raw time-domain products sum to the flat 3·VI·cos φ
let p_total: f64 = (0..3)
.map(|k| {
let theta = omega * t - 2.0 * PI * k as f64 / 3.0;
(2.0_f64).sqrt() * v * theta.cos() * (2.0_f64).sqrt() * i * (theta - phi).cos()
})
.sum();
assert!(
(p_total - 3.0 * v * i * phi.cos()).abs() < 1e-9,
"t={t}: total power is flat — the ripple left with the interference"
);
}
// the single-phase foil pulses: its ripple has no partners to cancel with
let single = |t: f64| {
let theta = omega * t;
2.0 * v * i * theta.cos() * (theta - phi).cos()
};
let swing = single(phi / (2.0 * omega)) - single(phi / (2.0 * omega) + PI / (2.0 * omega));
assert!(
(swing - 2.0 * v * i).abs() < 1e-9,
"one phase alone swings by 2VI — the pulsating torque three-phase removes"
);
}
#[test]
fn it_corrects_power_factor_by_rotating_home() {
// a 0.8-power-factor load draws S = [100, φ]: 80 kW of work, 60 kVAr of
// circulation. a capacitor injects the opp leg's opposite — [60, 3π/2] —
// and the sum rotates home to grade 0. same work, less current: the
// utility's whole business case in one geonum addition
let phi = 0.8_f64.acos();
let s_load = Geonum::new_with_angle(100.0, Angle::new(phi / PI, 1.0));
let capacitor = Geonum::new_with_angle(s_load.opp().mag, Angle::new(3.0, 2.0));
let s_corrected = s_load + capacitor;
assert!(s_corrected.near_mag(80.0), "|S| falls to the real power");
assert!(
s_corrected.opp().near_mag(0.0),
"the reactive leg is gone — unity power factor"
);
assert!(
(s_corrected.adj().mag - s_load.adj().mag).abs() < 1e-9,
"the real power is untouched — same work"
);
assert!(
(s_corrected.mag / s_load.mag - 0.8).abs() < 1e-12,
"line current drops 20% for the same delivered work"
);
}