added web front end

This commit is contained in:
2026-02-13 14:20:30 -06:00
parent 1d8e60c5df
commit f707c87f7e
5 changed files with 1906 additions and 250 deletions

View File

@@ -329,43 +329,34 @@ class ToroidSimulator:
turns_ratio = Ns / Np # a = Ns/Np
# --- Flux density ---------------------------------------------------
# The primary voltage applied to the winding minus the resistive drop
# sets the flux. We iterate once: compute ideal Ip, correct Vp seen by
# core, then recompute.
#
# Iteration:
# Pass 0 (ideal): B from full Vp
# Pass 1: B from (Vp - Ip_0 * Rp) where Ip_0 is ideal primary current
#
# --- Circuit solution (exact voltage divider) -----------------------
Ae_m2 = self._effective_Ae_mm2() * 1e-6
def _compute_op(Vp_core: float):
"""Compute operating point given effective core voltage."""
B = Vp_core / (4.44 * Np * Ae_m2 * freq_hz)
# Ideal open-circuit secondary voltage
Vs_oc = complex(Vp_core * turns_ratio, 0.0)
# Secondary loop: Vs_oc = Is * (Rs + Z_load)
Z_sec_total = complex(Rs, 0.0) + Z_load_complex
Is_phasor = Vs_oc / Z_sec_total
# Voltage across load
Vs_phasor = Is_phasor * Z_load_complex
# Reflect secondary current to primary (ideal transformer)
Ip_reflected = Is_phasor / turns_ratio # phasor, Amperes
return B, Is_phasor, Vs_phasor, Ip_reflected
# Exact voltage divider: solve for Vp_core analytically.
#
# Circuit: Vp --[Rp]-- Vp_core --[ideal xfmr]-- [Rs + Z_load]
#
# Secondary impedance reflected to primary:
# Z_sec_ref = (Rs + Z_load) / turns_ratio²
# Voltage divider:
# Vp_core = Vp * Z_sec_ref / (Rp + Z_sec_ref)
# Then:
# Is = Vp_core * turns_ratio / (Rs + Z_load)
# Ip = Is * turns_ratio (= Vp_core / Z_sec_ref / turns_ratio ... simplified)
# Pass 0: ideal (ignore primary drop for now)
_, Is0, _, Ip0 = _compute_op(Vp_rms)
Ip0_mag = abs(Ip0)
Z_sec_total = complex(Rs, 0.0) + Z_load_complex
Z_sec_ref = Z_sec_total / (turns_ratio ** 2) # reflected to primary
Z_total_primary = complex(Rp, 0.0) + Z_sec_ref
# Pass 1: correct for primary winding voltage drop
# Primary drop is Ip * Rp (in phase with current; Rp is real)
# Vp_core ≈ Vp_rms - Ip0 * Rp (phasor subtraction)
# For simplicity treat Ip0 as real-valued magnitude for the correction
# (conservative: subtracts in phase with voltage)
Vp_core = max(Vp_rms - Ip0_mag * Rp, 0.0)
Vp_core_phasor = complex(Vp_rms, 0.0) * Z_sec_ref / Z_total_primary
Vp_core = abs(Vp_core_phasor)
B_peak_T, Is_phasor, Vs_phasor, Ip_phasor = _compute_op(Vp_core)
B_peak_T = Vp_core / (4.44 * Np * Ae_m2 * freq_hz)
Vs_oc = Vp_core_phasor * turns_ratio
Is_phasor = Vs_oc / Z_sec_total
Vs_phasor = Is_phasor * Z_load_complex
Ip_phasor = Is_phasor * turns_ratio
Is_rms = abs(Is_phasor)
Vs_rms_out = abs(Vs_phasor)
@@ -876,69 +867,69 @@ if __name__ == "__main__":
constraints = SimConstraints(
B_max_T=1.0,
Vp_max=50.0,
Vs_max=90.0,
Ip_max=5.0,
Vs_max=120.0,
Ip_max=3.0,
Is_max=2.0,
P_out_max_W=25.0,
)
print("=== Single operating point (10 ohm resistive load) ===")
result = sim.simulate(
Vp_rms=12.0,
freq_hz=256.0,
primary_tap=2,
secondary_tap=1,
Z_load=(100.0, 0.0),
constraints=constraints,
)
print(result)
print()
# print("=== Single operating point (10 ohm resistive load) ===")
# result = sim.simulate(
# Vp_rms=12.0,
# freq_hz=256.0,
# primary_tap=2,
# secondary_tap=1,
# Z_load=(100.0, 0.0),
# constraints=constraints,
# )
# print(result)
# print()
print("=== Complex load (8 ohm + 1 mH at 50 kHz -> X ~= 314 ohm) ===")
X = 2 * math.pi * 50_000.0 * 1e-3
result2 = sim.simulate(
Vp_rms=24.0,
freq_hz=50_000.0,
primary_tap=2,
secondary_tap=1,
Z_load=(8.0, X),
constraints=constraints,
)
print(result2)
print()
# print("=== Complex load (8 ohm + 1 mH at 50 kHz -> X ~= 314 ohm) ===")
# X = 2 * math.pi * 50_000.0 * 1e-3
# result2 = sim.simulate(
# Vp_rms=24.0,
# freq_hz=50_000.0,
# primary_tap=2,
# secondary_tap=1,
# Z_load=(8.0, X),
# constraints=constraints,
# )
# print(result2)
# print()
print("=== Tap sweep ===")
all_results = sweep_taps(sim, 24.0, 50_000.0, (10.0, 0.0), constraints)
for r in all_results:
feasible = "OK" if r.feasible else "VIOL"
print(
f" P{r.primary_tap}/S{r.secondary_tap} "
f"Np={r.Np_eff:3d} Ns={r.Ns_eff:3d} "
f"Vs={r.Vs_rms:.3f}V Is={r.Is_rms:.4f}A "
f"P_out={r.P_out_W:.3f}W eff={r.efficiency*100:.2f}% [{feasible}]"
)
# print("=== Tap sweep ===")
# all_results = sweep_taps(sim, 24.0, 50_000.0, (10.0, 0.0), constraints)
# for r in all_results:
# feasible = "OK" if r.feasible else "VIOL"
# print(
# f" P{r.primary_tap}/S{r.secondary_tap} "
# f"Np={r.Np_eff:3d} Ns={r.Ns_eff:3d} "
# f"Vs={r.Vs_rms:.3f}V Is={r.Is_rms:.4f}A "
# f"P_out={r.P_out_W:.3f}W eff={r.efficiency*100:.2f}% [{feasible}]"
# )
print()
print("=== Optimize: find best taps + Vp to deliver ~10 W at 256 Hz ===")
opt = sim.optimize(
freq_hz=256.0,
Z_load=(10.0, 0.0),
target_power_W=25.0,
constraints=constraints,
Vp_min=1.0,
Vp_max=50.0,
Vp_steps=200,
power_tol_pct=2.0,
)
if opt is None:
print(" No feasible solution found.")
else:
print(opt)
# print()
# print("=== Optimize: find best taps + Vp to deliver ~10 W at 256 Hz ===")
# opt = sim.optimize(
# freq_hz=256.0,
# Z_load=(10.0, 0.0),
# target_power_W=25.0,
# constraints=constraints,
# Vp_min=1.0,
# Vp_max=50.0,
# Vp_steps=200,
# power_tol_pct=2.0,
# )
# if opt is None:
# print(" No feasible solution found.")
# else:
# print(opt)
print()
print("=== Operating-point sweep (3 freqs x 3 loads) ===")
freqs = [256.0, 870.0, 3140.0, 8900.0]
loads = [(50.0, 0.0), (100.0, 0.0), (200.0, 0.0), (600.0, 0.0)]
loads = [(10.0, 0.0), (50.0, 0.0), (100.0, 0.0), (200.0, 0.0), (600.0, 0.0), (2000.0, 0.0)]
entries = sweep_operating_points(
sim=sim,
frequencies=freqs,