Dyad component reference
What each component in the library offers. For building and running a model, see Building a model.
The circuit equations are authored in dyad/ecm.dyad. EquivalentCircuitCell selects the topology through the structural ECMTopology enum:
| Choice | Circuit |
|---|---|
ECMTopology.Rint() | OCV and series resistance |
ECMTopology.Thevenin() | n_rc polarization branches |
ECMTopology.DualPolarization() | Two polarization branches |
ECMTopology.PNGV() | One polarization branch and local OCV-drift capacitor |
ECMTopology.BulkSurface() | Linear SAFT bulk/surface capacitor network |
ECMTopology.ChenRinconMora() | Published SOC-dependent two-RC fit |
EDLC, the double-layer capacitor cell, has no ECMTopology variant. Exposing it here needs a native circuit component and a matching BatteryParameterSet entry for the device data; until then it is built from Julia, with BatteryCell(; model = EDLC(), chemistry = MaxwellPC2500()).
For example, this Dyad declaration selects a dual-polarization cell:
battery = BatteryComponents.EquivalentCircuitCell(
topology=BatteryComponents.ECMTopology.DualPolarization(),
capacity=100.0, SOC_initial=0.5,
R0=0.02402, R_dp=[0.00064, 0.00824], C_dp=[5630.0, 54277.0])Connect its p and n electrical pins and supply battery.ocv in volts from an OCV component. Generic Rint and Thevenin circuits deliberately accept an OCV signal so the user can choose the chemistry's OCV law. PNGV uses the fixed U0 parameter and a drift capacitor instead; the bulk/surface circuit uses U0 only for initialization. Chen–Rincón-Mora includes its published OCV law.
All parameters use SI units, except capacity in Ah and SOC as a fraction. Resistance and capacitance inputs to the reusable primitives may be functions of SOC supplied by other native components. Positive reported I means discharge; connector current follows the passive convention, so p.i = -I. A resistive load therefore receives positive power while SOC decreases.
Packs use the existing setup
BatteryCell, BatteryPack and CyclingCircuit retain their existing interface and now use the same Dyad-generated polarization, charge-storage and ohmic components. The adapter provides the parameter dictionaries, legacy variable names, stop bounds and thermal/pack integration. It does not maintain a second handwritten copy of the polarization or capacitor dynamics.
using BatteryComponents, ModelingToolkit
@named pack = BatteryPack(model=ECMTopology.DualPolarization(),
chemistry=He2011LiMn2O4(), series=2, parallel=3)
@named circuit = CyclingCircuit(model=ECMTopology.DualPolarization(),
chemistry=He2011LiMn2O4(), series=2, parallel=3,
protocol=[discharge(0.3C), rest(time=9000)])The existing Julia selectors (DP(), Thevenin(2), etc.) remain supported. A first-order Thevenin() defaults to the one-RC He parameter set; the two-RC model defaults to Chen–Rincón-Mora. Higher orders require an explicit parameter set, since no higher-order fit is supplied.
All battery models use the same electrical interface: current entering p charges the battery, and reported discharge-positive current is I = -p.i. A passive load therefore receives power while SOC falls. The same Cycler connects to ECM, SPM, SPMe and DFN cells without a model-specific setting.
The Julia parameter-dictionary API binds parameters and signals to the native RintCircuit, TheveninCircuit, PNGVCircuit or BulkSurfaceCircuit component. The convenience Dyad cells use these same circuits. Julia does not assemble a second topology or implement a separate set of circuit dynamics.
Tremblay–Dessaint empirical batteries
TremblayDessaint supplies the Shepherd-derived dynamic voltage law, with TremblayDessaint2009 parameters for Li-ion, lead-acid, NiMH and NiCd. Its equations live in dyad/tremblay_dessaint.dyad; TremblayDessaintLaw is the constitutive component and TremblayDessaintECM supplies electrical pins and coulomb counting. Pack equations lift the generated constitutive equations into whole (series, parallel) arrays.
@named battery = BatteryCell(model=TremblayDessaint())
@named pack = BatteryPack(model=TremblayDessaint(battery_type=:nimh),
chemistry=TremblayDessaint2009(battery_type=:nimh), series=2, parallel=3)The polarization branch follows the sign of the filtered current, whose time constant is 30 s. At reversal the instantaneous ohmic voltage changes immediately; the polarization coefficient changes when the filtered current crosses zero. Li-ion has an algebraic exponential in extracted Ah. The other chemistries carry an exponential-voltage state driven by absolute current, with a charge target A and a discharge target zero. Both charge integration and this state's rate convert seconds to hours explicitly.
The charging denominator is an explicit interpretation choice. Tremblay and Dessaint's equation (4) prints K*Q/(it-0.1Q), with a pole at it=0.1Q (90% SOC). Table 1's Li-ion parameters at a steady -2.3 A give 178.187641 V at 89.99% SOC and -171.412348 V at 90.01% SOC with that expression. This model implements K*Q/(it+0.1Q), as documented by MathWorks R2025a, so charging can traverse that region. The singular printed form is not selectable. For nickel cells the charge denominator uses abs(it)+0.1Q.
The discharge pole at it=Q is retained; charging past it=0 (SOC above 100%) stays finite for nickel cells through abs(it), and hits the documented Li-ion pole at it=-0.1Q. Parameters are constant and isothermal. Self-discharge, efficiency losses, prescribed health and thermal dynamics are unsupported. Q_gen reports only series-resistor heat, not a derived heat balance for the empirical polarization terms.
Table 1 labels its NiMH cell 6.5 Ah; section 4.1 identifies 7 Ah maximum capacity for its 1.3 A discharge. The parameter set uses that explicit maximum in the equations and preserves all table coefficients. The published 30-second time constant is distinct from the MathWorks interface's 95% response time.
The tests separate closed-form equation checks, section 4.1's three identification points and Figure 4's digitized simulation-curve replay; see test/tremblay_dessaint.jl for the digitization method and what the replay does and does not establish.
The Tremblay–Dessaint generated files use Dyad CLI 3.5.0, revision a0c9fcaf7ff6deec23938247e05245883a2aabe1, followed by JuliaFormatter 1.0.62.
ESC hysteresis and current-direction memory
ESC adds dynamic hysteresis and a discrete current-sign memory to an arbitrary number of RC branches. PlettE2 supplies the author's 25 °C E2 parameter row and OCV grid. A cell or pack uses the same interfaces:
@named cell = BatteryCell(model=ESC(), chemistry=PlettE2())
@named pack = BatteryPack(model=ESC(), chemistry=PlettE2(), series=4)
@named circuit = CyclingCircuit(model=ESC(), chemistry=PlettE2(),
protocol=[discharge(0.3C), rest(time=600)])For discharge-positive current, h is a voltage loss, and s is the last nonzero current direction. The terminal equation is V = OCV + M0*s - h - sum(R_j*i_Rj) - R0*I. Plett's signed hysteresis voltage is -h; the source's positive M0*s term is retained. Capacity is in Ah and time in seconds. Charge efficiency acts on coulomb counting and the rate of hysteresis change. RC branch currents follow the raw terminal current, as in the lecture's equations on pp. 2–14–2–15.
The "current sign threshold" defaults to 1e-8 A. Continuous events update s on outward crossings of +threshold and -threshold, with DAE reinitialization and a capped next step. Within the band, s holds its value. A current that jumps inside another event's affect, as at a CyclingCircuit step or an imposed setpoint change, crosses no root; a discrete event checked after every step applies the same update when s disagrees with the direction of a current outside the band. Initialization uses the solved initial current outside the band, or the supplied "initial current sign" inside it. The threshold does not modify the continuous hysteresis equation: at exactly zero current, h also holds its value. h is continuous at reversal. For constant M, its exact constant-current transition is a convex combination of its initial value and M*sign(I), which preserves [-M, M] when initialized inside that interval.
With M0 > 0, as in the published E2 fit, M0*s raises the voltage of a discharging cell and lowers that of a charging one. In a parallel group this is positive feedback: when the group current passes slowly through zero, the first cell to leave the threshold band draws a circulating current from the others. Driven by 2(1 - t) A, a 1 × 2 group of identical cells with the parameters of test/ecm_esc.jl carries -1.14 A and +0.14 A at t = 1.5 s, and which cell switches first is decided by roundoff. Identical parallel cells share current equally only while the group current stays outside the band or jumps across it inside an event, as a CyclingCircuit step does. A step written as a discontinuous function of time falls inside an integration step instead; driving a 2 × 2 group from 8 A to 0 that way, one parallel column switched to s = -1 alone and the group split in the same way.
The example pack is a series string. BatteryPack warns for parallel > 1 with a nonzero M0, which PlettE2 has; the ESC docstring explains why.
A current profile written as a piecewise-constant function of time, with its breakpoints passed as tstops, puts the sign root at a breakpoint whose current changes sign. The integrator shrinks its step to about 1e-11 s against the discontinuity, and the sign event with its DAE re-initialization follows that step. With the Dyad ecosystem pins (OrdinaryDiffEqBDF 2.4.5) the integration can then abort with ReturnCode.DtNaN. A one-RC cell driven through 4 A, 0 and −2 A this way failed right after the 0 → −2 A breakpoint when the breakpoints were also passed as d_discontinuities, and succeeded with tstops alone. A small DAE with a continuous event with DAE re-initialization at such a breakpoint fails with tstops alone as well, and with or without the min(dt, 3e-4) step cap that the event applies; without the event it succeeds, and OrdinaryDiffEqBDF 2.4.11 recovers in every case. Impose current steps through events instead, as a CyclingCircuit does, or through a discrete callback that changes the setpoint and re-initializes, as test/ecm_esc.jl does.
Setting M0 = 0 gives dynamic hysteresis alone. Setting M = 0 and h_initial = 0 gives instantaneous hysteresis alone.
Each branch current, h, and the one discrete sign field spans the complete (series, parallel) array. Circuit equation count depends on RC order, not pack size. The number of event roots scales with the number of cells so that cells can cross their thresholds independently.
The authoritative continuous relations are in dyad/esc.dyad, generated with Dyad CLI 3.5.0, revision a0c9fcaf7ff6deec23938247e05245883a2aabe1. Array assembly lifts those generated primitive equations. ESCCurrentSign supplies the external event implementation. ESCCircuit combines it with ESCHysteresis, ESCPolarization, and ESCTerminal.
The E2 parameters describe only 25 °C. The source archive contains a negative coulombic efficiency at -25 °C; this set neither includes nor repairs that row. The author's companion simCell.m also uses a Q/100 sign deadband and drives RC branches with efficiency-adjusted current. Those differ from the lecture relations used here. A simulation comparison must state which convention it uses. No held-out measured-data accuracy or hysteresis heat law is asserted.
Randles impedance circuits
Randles is the Randles circuit of Vandeputte et al. (arXiv:2305.15840, Fig. 2 and eq. (11)): an open-circuit voltage and a series resistance in series with a double layer in parallel with the charge-transfer resistance and a diffusion element. It is a time-domain EquivalentCircuitModel, used like the others:
chemistry = Vandeputte2023(open_circuit_voltage=3.6, nominal_capacity=4.8)
@named cell = BatteryCell(model=Randles(diffusion=:cpe), chemistry=chemistry)
@named pack = BatteryPack(model=Randles(order=8), chemistry=my_warburg_set,
series=4, parallel=2)The Warburg and CPE elements have no finite-dimensional time-domain form, so each is a bounded realization with a stated order and band. randles_impedance evaluates the exact impedance for comparison.
| Element | Realization | Order | Accurate band |
|---|---|---|---|
Finite-length Warburg, tanh or coth | Foster modes of the exact partial fractions (DLMF 4.36.2–4.36.3), with a static remainder resistor | order = N modes | 0 ≤ ω ≤ λ_N² / (8τ_d) (2.8/τ_d transmissive, 4.9/τ_d reflective at N = 2): < 0.9 % complex relative error, < 0.6 % magnitude, < 0.4° phase |
CPE 1/(Q s^α), including the semi-infinite Warburg α = 1/2 | Series RC network of Valsa, Dvořák and Friedl (2011), elements from the midpoint rule of the Stieltjes representation of s^-α over [f_low, f_high], with tail capacitor and resistor | order = M branches (double_layer_order for the double layer) | [100 f_low, f_high/100], any α in (0, 1), with ≥ 2 branches per decade (enforced): ≤ 1.2e-3 complex relative error, ≤ 1.02e-3 magnitude, ≤ 0.061° phase |
Outside its band a finite-length Warburg realization is resistive and a CPE ladder is a capacitor (below) or a resistor (above). All element values are positive, so the realizations are passive. The relations, including the element formulas, are in dyad/randles.dyad; the array pack lifts the generated primitives WarburgMode, WarburgRemainder, CPEMode, CPETail, RandlesCapacitor, ECMPolarization and RandlesTerminal, and the scalar cell is RandlesCircuit.
BatteryComponents.RandlesDoubleLayer — ModuleRandlesDoubleLayerDouble-layer element of RandlesCircuit: Capacitor (an ideal double-layer capacitance) or ConstantPhase (a CPE realized as an RC ladder over a stated band). Randles selects it with double_layer = :capacitor or :cpe.
BatteryComponents.RandlesDiffusion — ModuleRandlesDiffusionDiffusion element of RandlesCircuit: Transmissive or Reflective finite-length Warburg impedances realized by Foster modes, or ConstantPhase, a CPE realized as an RC ladder over a stated band. Randles selects it with diffusion = :transmissive, :reflective or :cpe.
The model is linear in the current about the open-circuit voltage, the setting in which the source identifies it (small zero-mean excitation at constant SOC). The reflective Warburg and a CPE store charge in a series capacitor, as the PNGV C_ocv does, so under a sustained current they add a drift to an SOC-dependent OCV. Vandeputte2023 transcribes the simulated impedance of Section 6; the source gives the impedance only, so the open-circuit voltage and capacity are required keywords and no configuration has a default parameter set. The Grünwald–Letnikov fractional ECM of Soleymani Aghdam, Alavi and Saif (arXiv:2103.00226) is a discrete-time model with a different topology (R∞ + R1‖CPE1 + CPE2); its identifiability result is not claimed for these circuits.
test/ecm_randles.jl checks the generated systems against an independently written state-space model under pulses, rest, reversal and a nonzero initial double-layer voltage; the linearized time-domain model against the exact impedance on the stated bands, including Section 6's [5 mHz, 100 Hz]; passivity; step-response convergence with the order against a numerical inverse Laplace transform of the exact impedance; charge and energy balances; packs, the cycler and prescribed health. No measured impedance data are replayed.
Health and degradation
ECMDegradation.NoDegradation() is the default: SOH and the resistance factor are one. The ECMs do not predict SEI growth or loss of active material. Those models in the electrochemical API depend on electrode states an ECM does not resolve.
ECMDegradation.PrescribedHealth() exposes capacity_health and resistance_growth inputs. The first is available capacity divided by nominal capacity; the second multiplies all circuit resistances. Capacitances and the OCV law are unchanged. These signals can come from a separately calibrated Dyad aging component or from measured health data. This option is not an aging prediction law and carries no chemistry-independent lifetime claim.
The normalized SOC equation is der(SOC) = -I / (3600 * capacity * SOH). This is an empirical coulomb-counting convention using instantaneous capacity, not a model of lithium inventory loss. Health must remain in (0, 1] and resistance scaling must remain positive. The native health component asserts these domains.
The same degradation enum is accepted by BatteryPack. For prescribed health, connect the pack's capacity_health and resistance_growth array inputs, each with shape (series, parallel). The pack reports mean SOH and aggregate available capacity. C-rate protocol setpoints continue to use the nominal capacity, as in the existing cycler.
Thermal interpretation
Q_loss reports resistor dissipation. It is a circuit accounting quantity, not an experimentally validated battery heat-generation model. In particular, a SOC-dependent fitted capacitance is not automatically a physical energy store with energy C*U^2/2. ECMThermalMass accepts a separately specified heat input and conserves heat through its two native thermal ports. The shared pack's temperature=true closure uses resistor dissipation and an optional entropic term; its physical adequacy must be checked for the chosen cell and operating conditions.
Validation and reproducibility
test/ecm_native.jl exercises the generated native components with electrical loads, current pulses, rest, charging, unrelaxed initial conditions, enum selection and prescribed health. References include exact exponential branch responses, the analytic bulk/surface solution, and charge/power balances. test/ecm.jl exercises the existing pack and cycler API, including series and parallel scaling for each circuit family.
test/reference/chen2006.jl independently integrates equations (1)–(7) of Chen and Rincón-Mora using an independent Julia RK4 implementation. Step halving from 0.5 s to 0.25 s changes the saved reference values by less than 1e-9. The checked-in reference covers 80 mA continuous discharge through 37000 s. These are numerical equation references, not measured data; the tests do not establish the paper's reported experimental voltage or runtime error.
He et al. supplies the circuit comparison and parameter tables. Table 5's printed capacitance and time constant disagree. The parameter set preserves the printed 54277 F; using 5427.7 F is an explicit alternative interpretation, not a confirmed correction. Parameter applicability and the local SOC window are documented under He2011LiMn2O4.
The generated files were produced with Dyad CLI 3.5.0, source revision a0c9fcaf7ff6deec23938247e05245883a2aabe1. Regenerate with dyad compile ., then format generated/ with JuliaFormatter 1.0.62, the same version used by this repository's format check. OrdinaryDiffEqDefault and RuntimeGeneratedFunctions are runtime dependencies because the compiler imports them in the generated component preamble. Markdown supplies the generated docstrings.
The shared BatteryPack builder represents a whole pack as a fixed set of array equations over the (series, parallel) axes, for both ECMs and electrochemical models, and a BatteryCell is the 1 × 1 pack. The equivalent circuits are written directly as those array equations. The electrochemical models are written as array equations over their spatial grids as well: every distributed quantity is one array with its grid axes ahead of the pack axes, so the number of equations depends on neither the pack dimensions nor the grid resolution. mtkcompile scalarizes the array equations again, so numerical storage and solve work still grow with the number of cells and grid points.
Native API
The generated API is included once in the API overview:
- Selection:
EquivalentCircuitCell,ECMTopology,ECMDegradation,ECMHealth. - Cells:
RintECM,TheveninECM,DualPolarizationECM,PNGVECM,BulkSurfaceECM,ChenRinconMoraECM,TremblayDessaintECM. - Parameter-signal circuits:
RintCircuit,TheveninCircuit,PNGVCircuit,BulkSurfaceCircuit. - Reusable components:
ECMOhmic,ECMPolarization,ECMOCVDrift,ECMBulkSurface,ECMCharge,ECMThermalMass,TremblayDessaintLaw. - Randles:
RandlesCircuit,WarburgMode,WarburgRemainder,CPEMode,CPETail,RandlesCapacitor,RandlesTerminal.
BatteryComponents.ECMTopology — ModuleECMTopologyDyad structural circuit selection: Rint, Thevenin, DualPolarization, PNGV, BulkSurface, or ChenRinconMora. Use these cases with EquivalentCircuitCell or the existing BatteryPack API.
BatteryComponents.ECMDegradation — ModuleECMDegradationDyad structural health selection. NoDegradation() fixes health at one; PrescribedHealth() accepts available-capacity and resistance-scaling signals from an independently calibrated aging model. It does not predict aging itself.
The shared ArrayBatteryPack selects its constitutive model, parameter set, thermal treatment and electrochemical degradation with five more structural enums:
BatteryComponents.BatteryModelFamily — ModuleBatteryModelFamilyStructural model family for ArrayBatteryPack: EquivalentCircuit, SingleParticle, SingleParticleElectrolyte, or DoyleFullerNewman. All families use the same pack network and electrical connector convention.
BatteryComponents.BatteryParameterSet — ModuleBatteryParameterSetParameter selection for ArrayBatteryPack: ModelDefault, NMC, LCO, LFP, NCA, He2011, or Chen2006. An incompatible model/parameter pairing is rejected. The electrical fits do not identify thermal or aging parameters.
BatteryComponents.BatteryThermalModel — ModuleBatteryThermalModelStructural thermal selection for ArrayBatteryPack: Isothermal, Lumped, or Distributed. Distributed temperature requires an electrochemical model. Adjacent parallel cells share thermal boundary temperatures and exchange heat.
BatteryComponents.BatteryElectrodeSelection — ModuleBatteryElectrodeSelectionElectrode selection for the sei and lam mechanisms in ArrayBatteryPack: Disabled, Negative, Positive, or Both. These mechanisms are available only for electrochemical models; ECM health is selected with ECMDegradation.
BatteryComponents.SEIParameterSet — ModuleSEIParameterSetPublished SEI parameter set for ArrayBatteryPack: PETLION, Ramadass2004 or PyBaMM. It parameterises the SEI submodel that BatteryElectrodeSelection enables, so it has no effect with SEI growth disabled, and a non-default set is rejected for an equivalent circuit, which has no SEI submodel. The sets and what separates them are in BatteryComponents.SEI_degradation_default_params.