Electrothermal Workflow¶
This guide documents the supported backend path for coupled electrical + control + thermal simulation in PulsimCore.
1) Enable Global Loss and Thermal Blocks¶
Use simulation.enable_losses and simulation.thermal together:
simulation:
enable_losses: true
thermal:
enabled: true
ambient: 25.0
policy: loss_with_temperature_scaling
default_rth: 1.0
default_cth: 0.1
Field semantics:
enabled: enables thermal state integration.ambient: ambient temperature in degC.policy:loss_onlyorloss_with_temperature_scaling.default_rth: non-strict fallback for missing per-devicerth.default_cth: non-strict fallback for missing per-devicecth.
2) Thermal-Capable Component Types¶
component.thermal.enabled: true is supported for:
resistordiodemosfet(aliases:m,nmos,pmos)igbt(alias:q)bjt_npn(aliases:bjtnpn,bjt-npn)bjt_pnp(aliases:bjtpnp,bjt-pnp)
If thermal is enabled on an unsupported type, YAML parsing fails with:
PULSIM_YAML_E_THERMAL_UNSUPPORTED_COMPONENT
3) Per-Component Thermal Port¶
components:
- type: mosfet
name: M1
nodes: [gate, drain, source]
vth: 2.5
kp: 0.01
g_off: 1e-8
loss:
eon: 1.0e-6
eoff: 1.0e-6
err: 0.5e-6
thermal:
enabled: true
rth: 0.8
cth: 0.02
temp_init: 25.0
temp_ref: 25.0
alpha: 0.004
Datasheet switching-loss surface (optional, backend-evaluated):
components:
- type: mosfet
name: M1
nodes: [gate, drain, source]
loss:
model: datasheet
axes:
current: [0.0, 10.0, 20.0] # A
voltage: [0.0, 200.0, 400.0] # V
temperature: [25.0, 125.0] # degC
eon: [ ... flat table ... ] # J
eoff: [ ... flat table ... ] # J
err: [ ... flat table ... ] # J (optional)
Table order is row-major (current, voltage, temperature):
index = ((i_current * N_voltage) + i_voltage) * N_temperature + i_temperature.
Validation for datasheet loss model:
axes.current/voltage/temperaturemust be finite and strictly increasing- each table length must match
N_current * N_voltage * N_temperature - each energy sample must be finite and
>= 0
Validation rules when thermal.enabled: true:
rthfinite and> 0cthfinite and>= 0temp_init,temp_ref,alphafinite
Invalid ranges fail with:
PULSIM_YAML_E_THERMAL_RANGE_INVALID
Optional staged thermal networks:
thermal:
enabled: true
network: foster # single_rc | foster | cauer
rth_stages: [0.3, 0.7]
cth_stages: [0.01, 0.05]
temp_init: 25.0
temp_ref: 25.0
alpha: 0.004
Staged thermal validation:
rth_stagesandcth_stagesare required forfoster/cauer- stage arrays must be non-empty and same size
- each
rth_stages[i]finite and> 0 - each
cth_stages[i]finite and>= 0
Deterministic diagnostics:
PULSIM_YAML_E_THERMAL_NETWORK_INVALIDPULSIM_YAML_E_THERMAL_DIMENSION_INVALID
Optional shared sink coupling (multiple devices on one heatsink):
components:
- type: mosfet
name: M1
nodes: [gate, vin, sw]
thermal:
enabled: true
rth: 0.7
cth: 0.02
shared_sink_id: hs_main
shared_sink_rth: 0.35
shared_sink_cth: 0.10
- type: diode
name: D1
nodes: [0, sw]
thermal:
enabled: true
rth: 1.2
cth: 0.03
shared_sink_id: hs_main
shared_sink_rth: 0.35
shared_sink_cth: 0.10
Shared sink rules:
shared_sink_idgroups devices into one common sink.- all devices in the same
shared_sink_idmust use identicalshared_sink_rth/shared_sink_cth. shared_sink_rthmust be finite and> 0.shared_sink_cthmust be finite and>= 0.shared_sink_rth/shared_sink_cthwithoutshared_sink_idis invalid.
4) Closed-Loop Control Sampling (Important for Stability)¶
Control scheduling is primarily configured per control block via sample_time (Ts):
components:
- type: pi_controller
name: PI1
nodes: [vref, vout, 0]
kp: 0.08
ki: 100.0
sample_time: 100e-6 # Ts local (0 ou ausente => contÃnuo)
Legacy global fallback is still available via simulation.control:
simulation:
control:
mode: auto # auto | continuous | discrete
sample_time: 1e-4 # fallback global (legacy)
Behavior:
- per-block (
component.sample_time|ts|Ts): Ts = 0or missing: continuous execution for that block;Ts > 0: discrete execution with output hold between updates.auto:- if global
sample_timeis provided, uses global fallback; - if any control block already declares local
Ts, global PWM inference is disabled; - otherwise infers legacy fallback
Ts = 1 / f_pwm_maxfrompwm_generator/PWM sources. continuous: updates control blocks at every accepted solver step.discrete: applies global fallback sampling when localTsis not set.
Notes:
sample_time/ts/Tsis accepted only for control virtual blocks (not probes/scopes).- In YAML strict validation, legacy
mode: discretestill requires globalsample_time > 0. sample_holdhas independentsample_periodbehavior.
5) Strict vs Non-Strict Thermal Validation¶
Strict parser mode requires per-component rth and cth when thermal is enabled:
import pulsim as ps
opts = ps.YamlParserOptions()
opts.strict = True
parser = ps.YamlParser(opts)
If missing in strict mode:
PULSIM_YAML_E_THERMAL_MISSING_REQUIRED
In non-strict mode, missing rth/cth are filled from simulation.thermal.default_rth/default_cth, and warnings are emitted:
PULSIM_YAML_W_THERMAL_DEFAULT_APPLIED
6) Integrated Closed-Loop + Thermal YAML Example¶
schema: pulsim-v1
version: 1
simulation:
tstart: 0.0
tstop: 20e-3
dt: 1e-6
step_mode: variable
enable_events: true
enable_losses: true
control:
mode: auto
thermal:
enabled: true
ambient: 25.0
policy: loss_with_temperature_scaling
default_rth: 1.0
default_cth: 0.05
components:
- type: voltage_source
name: Vin
nodes: [vin, 0]
waveform: {type: dc, value: 12.0}
- type: voltage_source
name: Vref
nodes: [vref, 0]
waveform: {type: dc, value: 6.0}
- type: mosfet
name: M1
nodes: [0, vin, sw]
vth: 3.0
kp: 0.35
g_off: 1e-8
loss: {eon: 2e-6, eoff: 2e-6}
thermal: {enabled: true, rth: 1.0, cth: 0.05}
- type: diode
name: D1
nodes: [0, sw]
g_on: 350.0
g_off: 1e-9
thermal: {enabled: true, rth: 2.0, cth: 0.03}
- type: inductor
name: L1
nodes: [sw, vout]
value: 220u
- type: capacitor
name: Cout
nodes: [vout, 0]
value: 220u
- type: resistor
name: Rload
nodes: [vout, 0]
value: 8.0
thermal: {enabled: true, rth: 3.0, cth: 0.2}
- type: pi_controller
name: PI1
nodes: [vref, vout, 0]
kp: 0.08
ki: 100.0
output_min: 0.0
output_max: 0.95
anti_windup: 1.0
- type: pwm_generator
name: PWM1
nodes: [0]
frequency: 10000.0
duty_from_channel: PI1
duty_min: 0.0
duty_max: 0.95
target_component: M1
7) Python Result Consumption¶
SimulationResult keeps summary and per-component telemetry:
result.loss_summaryresult.thermal_summaryresult.component_electrothermalresult.virtual_channels(includes canonical thermal traces when enabled)result.virtual_channel_metadata
import pulsim as ps
parser = ps.YamlParser(ps.YamlParserOptions())
circuit, options = parser.load("examples/09_buck_closed_loop_loss_thermal_validation_backend.yaml")
options.newton_options.num_nodes = int(circuit.num_nodes())
options.newton_options.num_branches = int(circuit.num_branches())
sim = ps.Simulator(circuit, options)
result = sim.run_transient(circuit.initial_state())
for item in result.component_electrothermal:
print(
item.component_name,
"thermal=", item.thermal_enabled,
"Pavg=", item.average_power,
"Tfinal=", item.final_temperature,
"Tpeak=", item.peak_temperature,
)
t_m1 = result.virtual_channels["T(M1)"]
meta_m1 = result.virtual_channel_metadata["T(M1)"]
print("T(M1) samples:", len(t_m1), "time:", len(result.time))
print("metadata:", meta_m1.domain, meta_m1.source_component, meta_m1.unit)
Notes:
- All non-virtual components are listed in
component_electrothermal. - Components with thermal disabled are still reported with deterministic ambient-based thermal fields.
- When
enable_losses=trueandthermal.enabled=true, transient output includes canonical thermal traces inresult.virtual_channelsnamedT(<component_name>)(for thermal-enabled components). - When
enable_losses=true, transient output also includes canonical per-component loss traces: Pcond(<component>)Psw_on(<component>)Psw_off(<component>)Prr(<component>)Ploss(<component>)All these channels are aligned withresult.timeand tagged inresult.virtual_channel_metadatawithdomain="loss"andunit="W".- YAML parser validates
loss.eon/eoff/erras finite and non-negative; invalid values fail deterministically withPULSIM_YAML_E_LOSS_RANGE_INVALID. - Datasheet loss schema errors are deterministic:
PULSIM_YAML_E_LOSS_MODEL_INVALIDPULSIM_YAML_E_LOSS_AXIS_INVALIDPULSIM_YAML_E_LOSS_DIMENSION_INVALID- Runtime now enforces a deterministic post-run consistency guard between canonical
electrothermal channels and summary surfaces (
loss_summary,thermal_summary,component_electrothermal). Any mismatch beyond tolerance fails the run with a deterministic diagnostic. - Thermal traces are emitted only when all conditions hold:
simulation.enable_losses: truesimulation.thermal.enabled: truecomponent.thermal.enabled: true(or equivalent runtime thermal config)- Consistency constraints for each thermal-enabled component
X: component_electrothermal[X].final_temperature == last(T(X))component_electrothermal[X].peak_temperature == max(T(X))component_electrothermal[X].average_temperature == mean(T(X))
8) KPI Gate Integration¶
The electrothermal gate validates consistency KPIs:
component_coverage_ratecomponent_coverage_gapcomponent_loss_summary_consistency_errorcomponent_thermal_summary_consistency_error
Threshold files:
benchmarks/kpi_thresholds.yaml(optional in general gate)benchmarks/kpi_thresholds_electrothermal.yaml(required in electrothermal gate)
9) Backend Contract (GUI Integration)¶
For each accepted transient sample index k, backend guarantees:
result.time[k]exists and is monotonic.- every exported
result.virtual_channels[name][k]is aligned to the samek. - channel metadata exists in
result.virtual_channel_metadata[name].
Electrothermal canonical guarantees:
- thermal channels use
T(<component_name>). - loss channels use:
Pcond(<component>)Psw_on(<component>)Psw_off(<component>)Prr(<component>)Ploss(<component>)- thermal/loss channels are backend-computed physics outputs (not UI post-processing).
- reductions are internally consistent:
component_electrothermal[i].final_temperature == last(T(component))component_electrothermal[i].peak_temperature == max(T(component))component_electrothermal[i].average_temperature == mean(T(component))
10) GUI Responsibility Boundary¶
GUI should own:
- form/wizard UX for scalar and datasheet model entry.
- unit conversion helpers and validation message presentation.
- curve import UX (CSV/PDF digitization) before sending numeric arrays to backend.
- plotting and dashboard composition.
GUI must not own:
- synthetic thermal curve generation.
- reconstruction of switching/conduction losses from electrical channels.
- replacement or smoothing of backend physical traces.
- heuristic domain inference from channel names when metadata is available.
11) Scalar-to-Datasheet Migration¶
Use the dedicated migration guide:
docs/electrothermal-migration-scalar-to-datasheet.md