Standalone WebAssembly module exposing the OpenMagnetics
MagneticAdviser (core + coil
selection, simulation, scoring) to JavaScript through a single Emscripten
artifact (libMagneticAdviser.wasm.js + .wasm, ~26 MB with all MAS
catalogs embedded).
- Bindings at a glance
- Build
- Loading the module
- Quick start
- API reference
- Return shape
- Fast mode vs full mode
- Settings
- Demo page (
demo/) - Common patterns
- Gotchas
- Tests
| Function | Purpose | State |
|---|---|---|
adviser(...) |
Rank designs for a complete MAS Inputs document. | reads g_constraints, g_libraryContext, Settings |
topologyAdviser(...) |
Same, but builds the Inputs from a per-topology spec via the matching MKF converter. | same |
setAdviserConstraints(json) |
Set wire / shape / material filters + onlyStock. |
writes g_constraints (+ Settings for onlyStock) |
clearAdviserConstraints() |
Drop all filters and reset g_onlyStock. |
writes g_constraints |
getAdviserConstraints() |
Inspect the current filter state. | read-only |
setLibraryFromJson(text, mode) |
Install a per-process user catalog. | writes g_libraryContext |
clearLibrary() |
Drop the user catalog. | writes g_libraryContext |
simulate(masJson, fastMode) |
Run MagneticSimulator on a complete MAS. |
independent of stateful config |
simulateMagnetic(inputsJson, magneticJson, fastMode) |
Same, with Inputs+Magnetic split. | same |
list_topologies() |
JSON array of topology keys for topologyAdviser. |
read-only |
list_settings() |
JSON array of valid keys for the settingsJson arg. |
read-only |
Authoritative source: EMSCRIPTEN_BINDINGS block at the bottom of
src/libMagneticAdviser.cpp.
See BUILD.md for the full sequence (one-time ngspice
staging, MAS header generation order, debug flags). Quick recap once
emsdk is sourced:
mkdir -p build && cd build
emcmake cmake .. -G Ninja \
-DEMBED_MAS_CORES=ON -DEMBED_MAS_CORE_SHAPES=ON \
-DEMBED_MAS_CORE_MATERIALS=ON -DEMBED_MAS_WIRES=ON \
-DEMBED_MAS_WIRE_MATERIALS=ON -DEMBED_MAS_BOBBINS=ON \
-DEMBED_MAS_INSULATION_MATERIALS=ON
ninja CAS MAS # generate headers first (CAS.hpp race)
ninja libMagneticAdviser -j4Outputs land in build/: a 108 KB ES6 module and a 26 MB .wasm.
The build produces an ES module — serve the .js and .wasm from the
same directory:
<script type="module">
import Module from './libMagneticAdviser.wasm.js';
const m = await Module(); // instantiates the WASM
const r = JSON.parse(m.adviser(JSON.stringify(inputs), 5, '{}', false, '', '{}'));
</script>In Node ≥ 18:
import Module from './build/libMagneticAdviser.wasm.js';
const m = await Module({
locateFile: (p) => './build/' + p, // tell Emscripten where the .wasm sits
print: () => {}, // silence MAS catalog load chatter
printErr: () => {},
});import Module from './build/libMagneticAdviser.wasm.js';
const m = await Module({ locateFile: p => './build/' + p });
// 1. Optionally narrow the candidate space. Filters persist across calls
// until cleared.
m.setAdviserConstraints(JSON.stringify({
allowedCoreShapeFamilies: ['ETD', 'PQ'],
allowedWireTypes: ['ROUND', 'LITZ'],
onlyStock: true,
}));
// 2. Run the topology adviser — caller has a Buck spec, library builds the
// MAS Inputs (process_design_requirements + ngspice).
const buck = {
inputVoltage: { minimum: 10, maximum: 12 },
diodeVoltageDrop: 0.7,
efficiency: 0.85,
currentRippleRatio: 0.4,
operatingPoints: [{
outputVoltages: [5], outputCurrents: [2],
switchingFrequency: 100000, ambientTemperature: 25,
}],
};
const r = JSON.parse(m.topologyAdviser('buck', JSON.stringify(buck),
3, '{}', /*fastMode*/ false, '', '{}'));
console.log(r.data[0].mas.magnetic.core.name, r.data[0].weightedTotalScoring);
// 3. Re-simulate the top pick with a different model toggle, etc.
const sim = JSON.parse(m.simulate(JSON.stringify(r.data[0].mas), /*fastMode*/ false));
console.log(sim.outputs[0].coreLosses.coreLosses);Direct path: takes a MAS Inputs document and returns ranked magnetic designs.
| Param | Type | Description |
|---|---|---|
inputsJson |
string | Stringified MAS Inputs ({designRequirements, operatingPoints}). |
maxResults |
number | Maximum designs to return. |
weightsJson |
string | Stringified {<MagneticFilter>: weight} map. Pass '{}' for default weights. Ignored when fastMode=true. |
fastMode |
boolean | true = analytical fast path (no ngspice/CoilAdviser, weights & CMC flow ignored). false = full path. |
coreModeString |
string | '' (default = STANDARD_CORES), "STANDARD_CORES", or "AVAILABLE_CORES". |
settingsJson |
string | Stringified {<setting-key>: <value>} map applied to MKF Settings before the run. '{}' to leave Settings alone. See list_settings() for valid keys. |
topologyAdviser(topology, topologyInputsJson, maxResults, weightsJson, fastMode, coreModeString, settingsJson) -> string
Converter path: builds the MAS Inputs from a per-topology spec by running
process_design_requirements() + ngspice waveform extraction internally,
then runs the adviser.
| Param | Type | Description |
|---|---|---|
topology |
string | Topology key — see list_topologies(). Case-insensitive; _/-/spaces are stripped ("flyback", "PHASE_SHIFTED_FULL_BRIDGE", "advancedLlc" all work). |
topologyInputsJson |
string | Stringified per-topology inputs (constructor argument of the matching MKF converter class). |
| Others | Same as adviser(). |
Topology coverage. 44 topologies registered as of this build: Buck, Boost, Cuk, Sepic, Zeta, FourSwitchBuckBoost, Flyback, IsolatedBuck/BuckBoost, SingleSwitch/TwoSwitch/ActiveClampForward, PushPull, AsymmetricHalfBridge, PhaseShiftedFullBridge, PhaseShiftedHalfBridge, LLC, CLLC, DAB — each with an
advanced…variant.AdvancedFlybackskips ngspice and routes throughconverter.process()directly (smaller payload, faster).
Both pieces of state persist across calls until cleared. They're applied
inside every adviser branch via the MKF
MagneticAdviser::get_advised_magnetic(..., ctx, constraints) overloads
that wrap each call in a DatabaseFilterScope (RAII swap of the global
coreDatabase / wireDatabase). Simulation calls do not honor them.
Returns "{}" on success, {"error": "..."} on a parse/validation error
(unknown key, non-array value, non-bool onlyStock, etc.). All keys
optional; missing key = no filter on that dimension. A value passes a
dimension iff (allowed.empty() || allowed∋v) && !blocked∋v — i.e.
allow-list is opt-in; block-list is always active.
{
"allowedWireTypes": ["ROUND", "LITZ"],
"blockedWireTypes": ["FOIL"],
"allowedCoreShapeFamilies": ["ETD", "PQ", "E"],
"blockedCoreShapeFamilies": ["T"],
"allowedCoreMaterialTypes": ["ferrite"],
"blockedCoreMaterialTypes": [],
"onlyStock": true
}String matching is case-insensitive against the magic_enum name of the
corresponding MAS enum ("ETD" / "etd" / "Etd" all match
CoreShapeFamily::ETD).
onlyStock is a thin convenience over Settings::set_use_only_cores_in_stock.
Toggling it forces a core-catalog reload on the next call because the
stock vs full catalog (cores_stock.ndjson vs cores.ndjson) is chosen
at load_cores() time and cached.
Pass "{}" to clear, or use clearAdviserConstraints() for the same
effect.
Resets all filter sets to empty and drops the onlyStock preference.
Does not revert the MKF Settings value for use_only_cores_in_stock
— if you previously flipped it via onlyStock, call
setAdviserConstraints({onlyStock: <previous>}) to flip it back
explicitly.
Returns the current state for debugging:
{
"wireType": { "allowed": [...], "blocked": [...] },
"shapeFamily": { "allowed": [...], "blocked": [...] },
"coreMaterialType": { "allowed": [...], "blocked": [...] },
"onlyStock": true
}onlyStock is omitted from the response when unset.
Installs a per-process user catalog (LibraryContext) consumed by every
subsequent adviser call. Returns "{}" on success or
{"error": "..."}. Schema is anything LibraryContext::loadFromString
accepts — keys include coreShapes, coreMaterials, cores, wires,
wireMaterials, bobbins, insulationMaterials (each may be a
{name: entry} map or an array of entries with a name field).
mode |
Effect |
|---|---|
"merge" |
Supplements the built-in catalog (default if mode is empty). |
"replace" |
The override fully replaces the corresponding built-in map. |
Repeated calls accumulate into the same context; clearLibrary() drops
it entirely.
Drops the user library context. Subsequent adviser calls revert to the built-in catalogs.
Both entry points run MagneticSimulator and return the fully-populated
MAS (with outputs[] filled in: core losses, winding losses,
magnetizing inductance, leakage, temperature, etc.). Neither honors
setAdviserConstraints (those filter candidates; simulation operates on
a chosen design) or setLibraryFromJson (the MAS is self-contained —
core, material, and wires are embedded in magnetic).
| Param | Type | Description |
|---|---|---|
masJson |
string | Stringified MAS ({inputs, magnetic} at minimum). Coil must be fully processed (turn descriptions present). |
fastMode |
boolean | true = analytical estimates only; false = full simulation with iterative loss/temperature convergence. |
A MAS produced by adviser(..., fastMode=false, ...) qualifies. A
fast-mode MAS does not and will throw
COIL_NOT_PROCESSED ... Missing turns description (see
Gotchas).
Same simulation with Inputs and Magnetic supplied separately. Matches
MagneticSimulator::simulate(Inputs, Magnetic, bool) 1:1. Useful when
the caller already has the two pieces split (e.g. resimulating a chosen
core against a new operating point).
Returns a stringified JSON array of all topology keys accepted by
topologyAdviser(). Useful for populating UI dropdowns without
hard-coding the list.
Returns a stringified JSON array of all setting keys accepted in the
settingsJson argument of adviser() / topologyAdviser(). As of this
build, 64 settings are registered.
Adviser entry points return a stringified JSON object. On success:
{
"data": [
{
"mas": { ... full MAS object ... },
"weightedTotalScoring": 4.0,
"scoringPerFilter": { "COST": 1.0, "LOSSES": 0.95, ... }
},
...
]
}Sorted by weightedTotalScoring descending. On error:
{ "error": "<message>" }simulate() / simulateMagnetic() return the simulated MAS directly
(no data wrapper) on success, same {error} shape on failure.
The library never throws into JS — C++ exceptions are caught at the JS boundary (try/catch in every entry point) and converted to the error payload above. The boundary handler is a translator, not a safety net: do not extend the pattern deeper into the code to swallow logic errors.
| Aspect | fastMode=true |
fastMode=false |
|---|---|---|
| Pipeline | get_advised_magnetic_fast() only |
full MagneticAdviser flow |
| Coil processing | Skipped — no turn placement | Full CoilAdviser + MagneticSimulator |
| ngspice waveform path | Skipped | Used by topologyAdviser only |
| Weights | Ignored — ranks by total analytical losses | Honored (or default COST+LOSSES+DIMENSIONS) |
| CMC (interference) flow | Ignored | Honored via the dedicated CMC filter flow |
| Typical wall-clock | 1–2 s per call (small N) | 8–30 s per call (varies wildly with N & spec) |
simulate() input? |
No — fast-mode MAS will throw | Yes |
| Constraints honored? | Yes | Yes |
Use fast mode for interactive UI ranking, panel sweeps, or first-pass exploration. Use full mode whenever the downstream caller needs a fully-processed coil (simulation, loss breakdown, plotting) or whenever custom weights / CMC filter flow matter.
The settingsJson argument is a {key: value} map applied to MKF
Settings before each adviser call. Settings persist (Settings is a
singleton) — passing them in settingsJson makes the change explicit
per call, but it does not roll back afterward. Get the full list at
runtime via list_settings().
Selected high-impact keys:
| Key | Type | Notes |
|---|---|---|
use_only_cores_in_stock |
bool | Loads cores_stock.ndjson instead of cores.ndjson. Also reachable via setAdviserConstraints({onlyStock: ...}). |
use_toroidal_cores |
bool | Include / exclude toroidal cores. |
use_concentric_cores |
bool | Include / exclude two-piece-set / piece-and-plate cores. |
use_powder_cores |
bool | Include / exclude powder materials. |
core_adviser_include_stacks |
bool | Expand stackable shapes (E, T, U, C, PLANAR_E) to multi-stack variants. |
core_adviser_include_distributed_gaps |
bool | Allow cores with one gap per column. |
core_adviser_saturation_margin |
double | Bpeak × margin > Bsat ⇒ reject. Default 1.2. Set to 1.0 for legacy behavior. |
coil_adviser_maximum_number_wires |
size_t | Cap on wires evaluated per winding. |
Mutating Settings has the same caching caveat as onlyStock — flags
consulted at load_cores() time (use_toroidal_cores,
use_concentric_cores, use_only_cores_in_stock) only take effect
after the catalog reloads. Toggling those keys from settingsJson
without invalidating the catalog will look like a no-op.
A single-file static page that exercises every binding through buttons + textareas. To use:
# from the repo root, after building build/libMagneticAdviser.wasm.{js,wasm}
python3 -m http.server 8765
# open http://localhost:8765/demo/Panels:
| # | Panel | What it covers |
|---|---|---|
| 1 | setAdviserConstraints / clear / get |
Checkboxes for wire types, shape families, material types; onlyStock toggle; live getAdviserConstraints round-trip view. |
| 2 | setLibraryFromJson / clearLibrary |
Paste a LibraryContext JSON, pick merge/replace. |
| 3 | topologyAdviser |
Topology dropdown (populated from list_topologies()), JSON textarea pre-seeded with the buck fixture, fastMode/coreMode controls. |
| 4 | adviser |
Direct MAS Inputs. "Load flyback fixture" button pulls test/fixtures/inputs_flyback_65W.json. |
| 5 | simulate / simulateMagnetic |
"Use last result" button pipes the most recent adviser output. Gated: refuses to populate from a fast-mode result with a clear message. |
| 6 | list_topologies / list_settings |
One-click introspection. |
| 7 | Runtime benchmark | Sweep maxResults across fast / full modes; pivoted table with per-row timing and an optional speedup column. |
The page exposes the WASM module as window.M, so the browser console
is also a usable REPL.
Compare fast vs full on the same input — handy when calibrating expectations:
const fastR = JSON.parse(m.topologyAdviser('buck', payload, 5, '{}', true, '', '{}'));
const fullR = JSON.parse(m.topologyAdviser('buck', payload, 5, '{}', false, '', '{}'));Resimulate the top pick under different Settings — adviser cached the magnetic; only the simulator inputs change:
const top = fullR.data[0].mas;
// Bump the saturation margin and rerun
const sim = JSON.parse(m.simulate(JSON.stringify(top), /*fastMode*/ false));Drop one MAS database to shrink the artifact — pass
-DEMBED_MAS_WIRES=OFF (and friends) to CMake. Be aware that without
the wire catalog any non-fast adviser call will fail to wind the coil.
Drive the demo programmatically — every button delegates to a
window.* function. Useful for E2E tests:
M.setAdviserConstraints(JSON.stringify({ allowedCoreShapeFamilies: ['PQ'] }));
document.querySelector('button[onclick="runTopology()"]').click();COIL_NOT_PROCESSED ... Missing turns description—simulate()on a fast-mode MAS. Fast mode skipsCoilAdviser, somagnetic.coilhas noturnsDescription. Re-run the adviser withfastMode=falseand feed that MAS tosimulate().onlyStocktoggle "doesn't take effect" — the core catalog is cached. The binding handles this for you by callingclear_loaded_cores()whenever the flag flips. If you mutateuse_only_cores_in_stock(oruse_toroidal_cores,use_concentric_cores) throughsettingsJsoninstead, you must flip the flag through a path that invalidates the cache or restart the process.clearAdviserConstraintsdoes not undoonlyStock— it drops the WASM-side preference, but the MKFSettingsvalue persists. Pass{onlyStock: <previous>}explicitly if you need to revert.list_topologies()returns 44 keys but my topology isn't there — some MKF converter models are stubs (e.g. rawCurrentTransformer,Cllllcskeleton) and are intentionally not registered. See the include list insrc/libMagneticAdviser.cppfor the authoritative set.- Custom weights with
fastMode=true— silently ignored (the fast path ranks by analytical losses internally). Use full mode if weights matter. - CMC / interference suppression inputs — full mode auto-detects
Application::INTERFERENCE_SUPPRESSIONand switches to the CMC filter flow (toroidal-only, impedance / leakage scoring). Fast mode does not. - ngspice 404 at build time — the MKF CMake tries to download
ngspice from a dead sourceforge mirror. Copy a working
_deps/ngspice/installfrom another WebLib build intobuild/_deps/ngspice/install/to skip the download. SeeBUILD.md§2. - Settings mutations persist across calls —
Settingsis a singleton, andsettingsJsondoesn't roll back. Track what you changed and reset it explicitly if you need a clean baseline.
node test/run.mjs # 11-case smoke suite (default — all sections)
node test/run.mjs adviser # only the direct adviser cases
node test/run.mjs topology # only the topologyAdviser cases
node test/run.mjs constraints # only the constraints+filter cases
node test/run.mjs simulate # only the simulate / simulateMagnetic cases
node test/run_matrix.mjs # full topologies × sizes sweep (long-running)Exit code 0 means all selected cases returned valid {data: [...]} (or
a populated MAS for simulate). Fixtures live in test/fixtures/; the
matrix runner writes per-case CSV to test/results_fast.log.
Open bugs / triage notes from the matrix runs are tracked in
HANDOFF.md (WASM-side) and
MKF_ISSUES.md (MKF-side). Read these before chasing
a failing topology — the bug is often already documented.