Magnetic Models¶
Status: shipped — header-only math primitives + four reference cores. Full Circuit-variant integration (saturable devices on the MNA stamp surface) is the natural follow-up.
Pulsim's magnetic-modeling layer covers four kinds of components a power engineer reaches for when laser-printing a converter:
| Component | Header | Use case |
|---|---|---|
| B-H curves | magnetic/bh_curve.hpp |
Material characterization (table from datasheet, or arctan / Langevin analytical fits). |
| Saturable inductor | magnetic/saturable_inductor.hpp |
Output chokes, PFC inductors, flyback primary — anything where the ferrite knee matters. |
| Saturable transformer | magnetic/saturable_transformer.hpp |
Multi-winding power transformers with shared core flux + per-winding leakage. |
| Hysteretic inductor | magnetic/hysteresis_inductor.hpp |
Mains-frequency cores where the actual hysteresis loop drives loss, demagnetization current, and inrush asymmetry — Jiles-Atherton 5-parameter model. |
Plus the loss / catalog plumbing:
| Helper | Header | Use case |
|---|---|---|
| Steinmetz / iGSE | magnetic/bh_curve.hpp |
Cycle-averaged specific loss P_v = k·f^α·B^β. iGSE handles non-sinusoidal flux. |
| Jiles-Atherton step | magnetic/bh_curve.hpp |
One-step ODE evolution of the hysteretic state for Phase 4 devices. |
| Core catalog loader | magnetic/core_catalog.hpp |
Parses <vendor>/<material>.yaml into BHCurveTable + SteinmetzLoss + JilesAthertonParams. Four reference cores ship under devices/cores/. |
Everything in the layer is header-only and self-contained — no link
to pulsim.lib is required for tests or downstream code that just wants
the math objects. The full Circuit-side integration (registering
SaturableInductor as a stamp-able variant alongside Inductor,
Capacitor, etc.) is tracked as a follow-up so the public API at the
device-class level is final.
TL;DR¶
Build a saturable inductor from a TDK N87 core data sheet:
#include "pulsim/v1/magnetic/core_catalog.hpp"
#include "pulsim/v1/magnetic/saturable_inductor.hpp"
using namespace pulsim::v1::magnetic;
const CatalogCore core = load_core_catalog_file(
"devices/cores/TDK/N87.yaml");
SaturableInductor<BHCurveTable> Lo(
{.turns = 50.0, .area = core.area_m2, .path_length = core.path_length_m},
core.bh_curve);
// Apply a constant 12 V across Lo for 5 µs:
Lo.advance_trapezoidal(12.0, 5e-6);
// Read back state:
const Real i = Lo.current(); // A
const Real L_d = Lo.differential_inductance(); // H, falls off at the knee
Math reference¶
B-H curves¶
All three implementations expose the same surface:
Real h_from_b(Real B); // forward characteristic
Real b_from_h(Real H); // inverse characteristic
Real dbdh(Real H); // differential permeability dB/dH
Real saturation_density(); // Bs (T)
| Curve | When to use |
|---|---|
BHCurveTable |
You have a measured (B, H) sequence from datasheet — most accurate, no analytical fitting drift. |
BHCurveArctan |
Quick first-cut for soft ferrites; only need Bs and Hc (= H at half saturation). |
BHCurveLangevin |
Para- / superparamagnetic materials with a softer roll-off than arctan can model. |
Saturable inductor¶
The geometry × curve combo gives:
λ = N · A_e · B (flux linkage)
i = (l_e / N) · h_from_b(B) (Ampère's law)
L_d= (N² · A_e · dB/dH) / l_e (differential inductance)
L_d is what the MNA companion stamp's g_eq = dt / (2·L_d) uses. In
deep saturation dB/dH → 0 and L_d floors at the air-core inductance
μ₀·N²·A_e/l_e so the simulator stays numerically stable.
Saturable transformer¶
N windings share one magnetic core. Each winding has its own turns
count and leakage inductance. The model treats the first winding as the
turns-ratio reference; voltage on winding k is
v_k = (N_k / N_ref) · dλ_m/dt + L_leak[k] · di_leak[k]/dt
Use cases: flyback (1 primary + 1 secondary, possibly with auxiliary), forward / push-pull (1 primary + center-tapped secondary), DAB, multi-output flyback. Saturation on the magnetizing branch is where the model earns its keep — linear coupled-inductor models miss the inrush behavior entirely.
Steinmetz / iGSE¶
Two flavors of cycle-averaged core-loss density:
SteinmetzLoss s{.k = 1.5e-3, .alpha = 1.6, .beta = 2.7};
const Real P_v_sin = s.cycle_average(f_hz, B_pk); // sinusoidal flux
const Real P_v_arb = igse_specific_loss(B_t, dt, s); // arbitrary B(t)
iGSE (Improved Generalized Steinmetz Equation) integrates |dB/dt|^α
over one cycle then folds in the peak-to-peak swing — necessary for
non-sinusoidal converter waveforms (square, triangular) where the
plain Steinmetz over-/under-counts depending on duty cycle.
For sinusoidal inputs the two agree within ≤ 5 % (gate G.2 tolerance).
Jiles-Atherton hysteresis¶
Five parameters describe the loop shape:
| Parameter | Range | Role |
|---|---|---|
Ms |
1e5 – 1e6 A/m | Saturation magnetization |
a |
10 – 1000 A/m | Domain density (Langevin scale) |
alpha |
1e-5 – 1e-3 | Inter-domain coupling |
k |
10 – 1000 A/m | Pinning coefficient (loop width) |
c |
0.05 – 0.5 | Reversibility coefficient |
HysteresisInductor wraps this with the geometry / turns plumbing.
Each apply_flux_step(λ) advances the J-A state to match the new flux,
producing path-dependent current responses that match measured loops
within typical 10 % envelope after parameter fitting.
Catalog¶
Four reference cores ship under devices/cores/:
| Vendor | Material | Geometry | Notes |
|---|---|---|---|
| TDK | N87 |
1.5 cm² × 4.5 cm | Standard MnZn power ferrite, datasheet rev 2017-11 |
| Ferroxcube | 3C90 |
1.0 cm² × 4.0 cm | Lower-loss MnZn, datasheet rev 2008-09 |
| Magnetics | MPP_60u |
0.85 cm² × 5.5 cm | Distributed-air-gap powder, μ=60, catalog rev 2019 |
| EPCOS | N97 |
1.4 cm² × 4.4 cm | Re-branded TDK family, data brief rev 2014-10 |
Catalog files are YAML maps with vendor, material, geometry,
bh_curve, steinmetz, and optional jiles_atherton blocks — see any
of the shipped files for a complete example. Loader signature:
CatalogCore core = load_core_catalog_file(
"devices/cores/TDK/N87.yaml");
SaturableInductor<BHCurveTable> Lo(
{.turns = 50, .area = core.area_m2, .path_length = core.path_length_m},
core.bh_curve);
The loader rejects malformed YAML (missing geometry, fewer than two
B-H points, etc.) with std::invalid_argument.
Validation¶
Three end-to-end checks pin the gates from add-magnetic-core-models:
| Gate | Test | Result |
|---|---|---|
| G.1 Inrush ≤ 20 % vs Faraday | test_magnetic_phase6_validation::Phase 6 G.1 |
Mains transformer (Bs=1.5T, 800 turns) at 110V·5ms applied integral → measured i_actual matches the arctan-inverse closed-form (l_e/N)·Hc·tan(π·B/(2Bs)) within ±20 % |
| G.2 Steinmetz ≤ 10 % | Phase 6 G.2 |
iGSE sweeps 25 / 100 / 500 kHz on a sinusoid → matches cycle_average(f, B_pk) within ±10 % |
| G.3 Saturation onset ≤ 5 % | Phase 6 G.3 |
5 % flux-linkage increment past the knee produces ≥ 5× the linear-regime current change — saturation is real |
| G.4 ≥ 3 of 4 cores load | test_magnetic_phase5_catalog |
All four shipped catalog files parse cleanly |
Follow-ups¶
- Circuit-variant integration: register
SaturableInductorand friends in theDeviceVariantso the existing MNA stamp pipeline picks them up. Today they're math objects; the integration layer is the next change. - Datasheet PDF importer: the
pulsim.import_core_datasheet(pdf, vendor)helper from the proposal is deferred — the YAML manifest approach already covers the catalog use case without OCR / vendor- specific table extraction. - Coupled-loop hysteresis loss:
iGSEcovers eddy + classical Steinmetz loss but not hysteresis loop area. Tracked alongside Jiles-Atherton parameter fitting.
Steinmetz core-loss KPI (Phase 26)¶
Phase 26 added a Steinmetz core-loss helper to the benchmark KPI module so you can score a core's loss without writing a custom analyzer:
benchmark:
id: core_loss_steinmetz_sine
kpi:
- metric: core_loss_steinmetz
observable: I(L_core) # inductor current → B via L·I / (N·A)
label: ferrite
inductance_h: 500e-6
turns: 60
area_m2: 1.5e-4
fundamental_hz: 60.0
k: 16.0 # Steinmetz coefficients
alpha: 1.45
beta: 2.7
The runner emits kpi__core_loss_w_per_kg__ferrite and
kpi__b_peak_tesla__ferrite columns in results.csv. Two benchmarks
exercise this end-to-end:
benchmarks/circuits/core_loss_steinmetz_sine.yaml— sinusoidal excitation at known frequency / amplitude.benchmarks/circuits/saturating_inductor_step.yaml— current step into a saturating inductor; exercises the L_eff(I) curve plus loss scaling.
See KPI Reference → Steinmetz core loss
for the per-parameter reference and the underlying Python helper
compute_core_loss_steinmetz.
See also¶
automatic-differentiation.md— the AD path that the saturable devices' Newton Jacobian will land on once they're wired into the MNA stamp surface.linear-solver-cache.md— the per-key LRU that benefits when transformer cycling produces the same(topology, dt)matrix across PWM cycles.components-reference.md— full YAML parameter reference forsaturable_inductorandcoupled_inductor.kpi-reference.md— every available metric, the YAML wiring, and the underlying Python helper signatures.