KPI Reference¶
Status: authoritative — every metric
benchmark_runner.pycan compute against a captured trace. Source:benchmarks/kpi/__init__.py(computation) andbenchmarks/benchmark_runner.py(YAML wiring). Updated through Phase 27.
Pulsim ships a trace-scoring layer that runs after the simulation and turns a captured CSV into engineering KPIs — THD, ripple, efficiency, ZVS/ZCS fractions, core loss, junction temperature, etc. The functions are pure Python with no Pulsim runtime dependency, so the same module can score Pulsim traces, PSIM / PLECS exports, or an oscilloscope CSV.
How KPIs land in benchmark results¶
Each circuits/<bench>.yaml may declare a benchmark.kpi: list. The
runner evaluates every entry against the trace and writes them as
kpi__<metric>__<label> columns in benchmarks/.../results.csv. KPI
thresholds (gate values used by kpi_gate.py) live in
benchmarks/kpi_thresholds.yaml.
benchmark:
id: my_buck
category: closed_loop
kpi:
- metric: ripple_pkpk
observable: V(out)
label: vout
steady_state_fraction: 0.2
- metric: transient_response
observable: V(out)
label: vout
target: 12.0
tolerance_pct: 2.0
- metric: thd
observable: I(L1)
label: iL
fundamental_hz: 60.0
num_harmonics: 20
The label becomes the suffix on the CSV column — kpi__ripple_pkpk__vout,
kpi__rise_time__vout, etc.
Quick index¶
| Metric | YAML keyword | Returns | Use case |
|---|---|---|---|
| Total harmonic distortion | thd |
thd_pct |
Sine quality, EMI compliance |
| Power factor | power_factor |
pf |
AC source loading |
| Efficiency | efficiency |
efficiency_pct |
Converter η |
| Transient response | transient_response |
rise_time, settling_time, overshoot_pct, undershoot_pct |
Step response analysis |
| Peak-to-peak ripple | ripple_pkpk |
ripple_pkpk |
Output filter sizing |
| Conduction + switching loss | loss_breakdown |
conduction_w_avg, switching_w_avg |
Loss budgeting |
| ZVS fraction | zvs_fraction |
zvs_fraction, total_turn_on_events |
Soft-switching validation |
| ZCS fraction | zcs_fraction |
zcs_fraction, total_turn_off_events |
Soft-switching validation |
| Per-event switching loss | switching_loss |
switching_loss_w_avg, switching_event_count |
High-frequency loss |
| Steinmetz core loss | core_loss_steinmetz |
core_loss_w_per_kg, b_peak_tesla |
Magnetic sizing |
| Junction temperature | junction_temperature |
t_j_max_c, t_j_final_c, delta_t_j_c |
Thermal margin |
1. Total Harmonic Distortion — thd¶
Function: compute_thd(samples, sample_rate_hz, fundamental_hz, num_harmonics=20)
→ percentage.
THD% = 100 · √(Σ_{k=2..N} A_k²) / A_1 where A_k is the magnitude of
the k-th harmonic recovered via Goertzel. The window should cover an
integer number of fundamental periods for a clean reading.
- metric: thd
observable: V(out)
label: vout
fundamental_hz: 60.0
num_harmonics: 20
| YAML key | Default | Meaning |
|---|---|---|
observable |
— | CSV column to FFT. |
fundamental_hz |
60.0 |
Hz of the fundamental. |
num_harmonics |
20 |
Harmonics to sum (k = 2..N). |
label |
metric |
Suffix in the result column. |
Outputs: kpi__thd_pct__<label>.
2. Power factor — power_factor¶
Function: compute_power_factor(v_samples, i_samples) → [-1, 1].
PF = ⟨v·i⟩ / (RMS(v)·RMS(i)). Sign tracks real-power direction.
- metric: power_factor
observable: V(grid)
current_observable: I(grid)
label: grid
Outputs: kpi__pf__<label>.
3. Efficiency — efficiency¶
Function: compute_efficiency(p_in, p_out, steady_state_fraction=0.2)
→ percent.
η = 100 · ⟨P_out⟩ / ⟨P_in⟩ averaged over the last fraction of the
trace, so transient warm-up doesn't pull the number down.
- metric: efficiency
observable: P_in
p_in_observable: P_in
p_out_observable: P_out
label: converter
steady_state_fraction: 0.2
| YAML key | Default | Meaning |
|---|---|---|
p_in_observable |
— | Column for input power (W). |
p_out_observable |
— | Column for output power (W). |
steady_state_fraction |
0.2 |
Window for the average (last fraction). |
Outputs: kpi__efficiency_pct__<label>.
4. Transient response — transient_response¶
Function: compute_transient_response(times, samples, target, tolerance_pct=2, rise_low_pct=10, rise_high_pct=90)
→ dict.
- metric: transient_response
observable: V(out)
label: vout
target: 12.0
tolerance_pct: 2.0
| YAML key | Default | Meaning |
|---|---|---|
target |
0.0 |
Steady-state target value. |
tolerance_pct |
2.0 |
Settling band (±%). |
Outputs (each populated when measurable):
- kpi__rise_time__<label> — rise_low_pct% → rise_high_pct% (default 10–90%).
- kpi__settling_time__<label> — time after which the trace stays inside the band.
- kpi__overshoot_pct__<label> — 100 · (peak − target) / |target|.
- kpi__undershoot_pct__<label> — 100 · (target − trough) / |target|.
5. Peak-to-peak ripple — ripple_pkpk¶
Function: compute_ripple_pkpk(samples, steady_state_fraction=0.2).
- metric: ripple_pkpk
observable: V(out)
label: vout
steady_state_fraction: 0.2
Outputs: kpi__ripple_pkpk__<label> in the units of the observable.
6. Conduction + switching loss — loss_breakdown¶
Function: compute_loss_breakdown(switch_states, branch_currents, branch_voltages, r_on, times)
→ {conduction_w_avg, switching_w_avg}.
Conduction = R_on · I² integrated while ON. Switching = ½ · |V·I|
at every state transition. Both divided by total simulated time.
- metric: loss_breakdown
observable: SW1.state # ignored; uses switch_observable
switch_observable: SW1.state
current_observable: I(SW1)
voltage_observable: V(sw)
r_on: 5e-3
label: SW1
Outputs: kpi__conduction_w_avg__<label>, kpi__switching_w_avg__<label>.
7. ZVS fraction — zvs_fraction¶
Function: compute_zvs_fraction(switch_states, v_ds_samples, threshold_v=1.0, lookback_samples=1).
For every False → True (turn-ON) transition, looks lookback_samples
back at V_DS. If |V_DS| < threshold_v, the event counts as ZVS.
- metric: zvs_fraction
switch_observable: SH.state
voltage_observable: V(sh)
threshold_v: 1.0
lookback_samples: 1
label: SH
Outputs: kpi__zvs_fraction__<label> (0..1), kpi__total_turn_on_events__<label>.
8. ZCS fraction — zcs_fraction¶
Function: compute_zcs_fraction(switch_states, i_d_samples, threshold_a=0.1, lookback_samples=1).
Mirror of ZVS but at turn-OFF and on drain current.
- metric: zcs_fraction
switch_observable: SH.state
current_observable: I(L1)
threshold_a: 0.1
lookback_samples: 1
label: SH
Outputs: kpi__zcs_fraction__<label>, kpi__total_turn_off_events__<label>.
9. Per-event switching loss — switching_loss¶
Function: compute_switching_loss_per_event(switch_states, v_ds, i_d, times).
Sums ½·|V_DS·I_D| at every state transition and divides by total time.
- metric: switching_loss
switch_observable: SH.state
voltage_observable: V(sh)
current_observable: I(L1)
label: SH
Outputs: kpi__switching_loss_w_avg__<label>, kpi__switching_event_count__<label>.
10. Steinmetz core loss — core_loss_steinmetz¶
Function: compute_core_loss_steinmetz(B_samples, sample_rate_hz, fundamental_hz, k, alpha, beta).
Recovers B_peak via Goertzel, then P = k · f^α · B^β. Pulsim ships a
companion helper compute_inductor_flux_density(I_samples, L, turns, area_m²)
to convert an inductor current trace into B(t).
- metric: core_loss_steinmetz
observable: I(L1) # inductor current → B via L·I / (N·A)
inductance_h: 500e-6
turns: 60
area_m2: 1.5e-4
fundamental_hz: 60.0
k: 16.0
alpha: 1.45
beta: 2.7
label: ferrite
| YAML key | Default | Meaning |
|---|---|---|
inductance_h |
1e-3 |
Inductance for the L·I conversion (H). |
turns |
50 |
Number of turns. |
area_m2 |
1e-4 |
Effective core cross-section (m²). |
fundamental_hz |
60.0 |
Operating frequency (Hz). |
k, alpha, beta |
16, 1.45, 2.7 |
Steinmetz coefficients for the material. |
Outputs: kpi__core_loss_w_per_kg__<label>, kpi__b_peak_tesla__<label>.
11. Junction temperature — junction_temperature¶
Function: compute_junction_temperature(P_samples, times, r_th_jc, c_th_jc, t_ambient_c=25, r_th_ca=0).
Integrates C_th · dT_j/dt + (T_j − T_amb)/R_th = P(t) forward in time
(Foster network, one or many stages chained).
The YAML wiring derives P(t) from an observed voltage trace through a resistance — useful for self-heating studies:
- metric: junction_temperature
observable: V(r_diss) # voltage across the dissipating element
r_resistor: 1.0 # to derive P(t) = V²/R
r_th_jc: 5.0 # K/W
c_th_jc: 0.1 # J/K (use 0 for steady-state-only)
t_ambient_c: 25.0
r_th_ca: 0.0 # heatsink-to-ambient (K/W), optional
label: r_diss
| YAML key | Default | Meaning |
|---|---|---|
r_resistor |
1.0 |
Resistance used to convert V² → P (Ω). |
r_th_jc |
5.0 |
Junction-to-case thermal resistance (K/W). |
c_th_jc |
0.1 |
Junction-to-case thermal capacitance (J/K). |
t_ambient_c |
25.0 |
Ambient temperature (°C). |
r_th_ca |
0.0 |
Case-to-ambient thermal resistance (K/W). |
Outputs: kpi__t_j_max_c__<label>, kpi__t_j_final_c__<label>,
kpi__delta_t_j_c__<label>.
Calling the helpers directly (Python)¶
The same functions are importable from Python without going through the runner — useful for one-off scoring of an oscilloscope CSV or a PSIM export:
from benchmarks.kpi import (
compute_thd,
compute_ripple_pkpk,
compute_transient_response,
compute_zvs_fraction,
compute_junction_temperature,
compute_inductor_flux_density,
compute_core_loss_steinmetz,
)
# THD on a captured current trace
thd_pct = compute_thd(i_load, sample_rate_hz=1e5, fundamental_hz=60.0)
# Junction temperature from a resistor's V trace
import benchmarks.kpi as kpi
P = kpi.compute_power_dissipation_resistor(v_a, v_b, resistance=1.0)
result = compute_junction_temperature(
P, times, r_th_jc=5.0, c_th_jc=0.1, t_ambient_c=25.0,
)
print(result["t_j_max_c"], "°C peak")
Every helper is pure Python (no Pulsim runtime) — they can be vendored into a separate tool.
KPI gating¶
The benchmarks/kpi_gate.py script reads benchmarks/kpi_thresholds.yaml
and fails a CI run if any KPI exceeds (or falls below) its threshold. The
threshold file follows the same kpi__<metric>__<label> naming as the
results CSV:
# kpi_thresholds.yaml
gates:
kpi__ripple_pkpk__vout:
max: 0.5
kpi__rise_time__vout:
max: 5e-3
kpi__zvs_fraction__SH:
min: 0.95
kpi__t_j_max_c__r_diss:
max: 150.0
Run it after benchmark_runner.py:
python3 benchmarks/benchmark_runner.py --benchmarks benchmarks/benchmarks.yaml \
--output-dir benchmarks/out
python3 benchmarks/kpi_gate.py --results benchmarks/out/results.csv \
--thresholds benchmarks/kpi_thresholds.yaml
The companion file benchmarks/kpi_thresholds_electrothermal.yaml
holds the dedicated thermal-margin gates used by the Phase 27
electrothermal benches.
See also¶
- Components Reference — for the components that produce the signals your KPIs read.
- Control Blocks Reference — for the
channels (
chan:PLL.theta,chan:SVM.d_a, etc.) you can score. - Magnetic Models — Steinmetz coefficients and flux-density definitions.
- Electrothermal Workflow — the full thermal
port and how to wire its outputs into
junction_temperature. - Benchmarks and Parity — runner CLI flags, baseline regeneration, dashboard outputs.