added toroid simulator

This commit is contained in:
2026-02-17 11:11:47 -06:00
parent a881a0a381
commit 4df588e09c
6 changed files with 323 additions and 108 deletions

View File

@@ -535,6 +535,29 @@
</div>
</div>
<!-- Operating Point -->
<div class="card">
<div class="card-header">Operating Point</div>
<div class="card-body">
<div class="form-row">
<label class="field"><span>Primary tap</span><input type="number" id="op-ptap" value="1" min="1" step="1"></label>
<label class="field"><span>Secondary tap</span><input type="number" id="op-stap" value="1" min="1" step="1"></label>
</div>
<div class="form-row">
<label class="field"><span>Vp RMS (V)</span><input type="number" id="op-vp" value="12" min="0.01" step="0.1"></label>
<label class="field"><span>Frequency (Hz)</span><input type="number" id="op-freq" value="1000" min="1" step="1"></label>
</div>
<div class="form-row">
<label class="field"><span>Load R (Ω)</span><input type="number" id="op-rload" value="100" min="0" step="1"></label>
<label class="field"><span>Load X (Ω)</span><input type="number" id="op-xload" value="0" step="1"></label>
</div>
<div class="btn-row">
<button class="btn btn-primary" id="btn-op" onclick="runOperatingPoint()">Simulate</button>
</div>
<div id="op-msg" class="msg" style="margin-top:8px"></div>
</div>
</div>
</div><!-- /left-col -->
@@ -552,6 +575,17 @@
</div>
</div>
<!-- Operating Point Results -->
<div class="card" id="op-results-card" style="display:none">
<div class="card-header">Operating Point Results</div>
<div class="card-body" style="padding:12px">
<div id="op-violations" style="display:none;margin-bottom:10px;padding:8px 12px;background:#fef2f2;border:1px solid #fca5a5;border-radius:6px;color:#b91c1c;font-size:12px"></div>
<table class="design-table" style="width:100%">
<tbody id="op-results-body"></tbody>
</table>
</div>
</div>
<!-- Plots -->
<div class="card" id="plots-card" style="display:none">
<div class="card-header">Simulation Results</div>
@@ -834,6 +868,93 @@ function parseLoads() {
}).filter(p => p[0] > 0 || p[1] !== 0);
}
// ============================================================
// Operating Point
// ============================================================
async function runOperatingPoint() {
const btn = document.getElementById('btn-op');
const msg = document.getElementById('op-msg');
const card = document.getElementById('op-results-card');
btn.disabled = true;
setMsg(msg, 'info', 'Simulating…');
try {
const payload = Object.assign({}, buildPayload(), {
primary_tap: parseInt(document.getElementById('op-ptap').value),
secondary_tap: parseInt(document.getElementById('op-stap').value),
Vp_rms: parseFloat(document.getElementById('op-vp').value),
freq_hz: parseFloat(document.getElementById('op-freq').value),
R_load: parseFloat(document.getElementById('op-rload').value) || 0,
X_load: parseFloat(document.getElementById('op-xload').value) || 0,
constraints: collectConstraints(),
});
const resp = await fetch('/api/simulate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
});
const data = await resp.json();
if (!data.success) {
setMsg(msg, 'error', 'Error: ' + data.error);
return;
}
setMsg(msg, 'ok', 'Done.');
card.style.display = '';
// Violations banner
const vdiv = document.getElementById('op-violations');
if (data.violations && data.violations.length) {
vdiv.style.display = '';
vdiv.textContent = 'Violations: ' + data.violations.join(', ');
} else {
vdiv.style.display = 'none';
}
const rows = [
['Turns (Np / Ns)', `${data.Np_eff} / ${data.Ns_eff}`],
['Turns ratio (Np:Ns)', data.turns_ratio != null ? data.turns_ratio.toFixed(4) : '—'],
['Peak flux density', data.B_peak_T != null ? data.B_peak_T.toFixed(4) + ' T' : '—'],
['Primary voltage (Vp)', data.Vp_rms != null ? data.Vp_rms.toFixed(3) + ' V' : '—'],
['Secondary voltage (Vs)', data.Vs_rms != null ? data.Vs_rms.toFixed(3) + ' V' : '—'],
['Primary current (Ip)', data.Ip_rms != null ? data.Ip_rms.toFixed(4) + ' A' : '—'],
['Secondary current (Is)', data.Is_rms != null ? data.Is_rms.toFixed(4) + ' A' : '—'],
['Output power', data.P_out_W != null ? data.P_out_W.toFixed(3) + ' W' : '—'],
['Copper loss (primary)', data.P_cu_primary_W != null ? data.P_cu_primary_W.toFixed(3) + ' W' : '—'],
['Copper loss (secondary)', data.P_cu_secondary_W != null ? data.P_cu_secondary_W.toFixed(3) + ' W' : '—'],
['Total copper loss', data.P_cu_W != null ? data.P_cu_W.toFixed(3) + ' W' : '—'],
['Core loss', data.P_core_W != null ? data.P_core_W.toFixed(3) + ' W' : '—'],
['Input power', data.P_in_W != null ? data.P_in_W.toFixed(3) + ' W' : '—'],
['Efficiency', data.efficiency_pct != null ? data.efficiency_pct.toFixed(2) + ' %' : '—'],
];
const tbody = document.getElementById('op-results-body');
tbody.innerHTML = rows.map(([label, val]) =>
`<tr><td style="font-weight:500;color:#444;width:55%">${label}</td><td>${val}</td></tr>`
).join('');
} catch (err) {
setMsg(msg, 'error', 'Error: ' + err.message);
} finally {
btn.disabled = false;
}
}
// Helper: collect constraints object from UI
function collectConstraints() {
return {
B_max_T: parseFloat(document.getElementById('con-B').value) || 0.3,
Vp_max: parseFloat(document.getElementById('con-Vp').value) || 1e9,
Vs_max: parseFloat(document.getElementById('con-Vs').value) || 1e9,
Ip_max: parseFloat(document.getElementById('con-Ip').value) || 1e9,
Is_max: parseFloat(document.getElementById('con-Is').value) || 1e9,
P_out_max_W:parseFloat(document.getElementById('con-Pout').value) || 1e9,
};
}
async function runSweep() {
const btn = document.getElementById('btn-sim');
const msg = document.getElementById('sim-msg');