- Component callbacks take a single symbol list.
ComponentCondition(f, syms)withf(u, t)andComponentAffect(f, syms)withf(u, ctx); the list may name states, parameters, inputs, outputs and observed alike, all reachable throughu. In an affect the states and parameters are writable, everything else is read only, so affects can now react to observed values directly. The values are a snapshot taken when the affect fires. The old(f, sym, psym)forms withf(u, p, t)andf(u, p, ctx)still work but warn once. - Affects can opt out of the automatic step-size reset with
ctx.dt_reset[] = false, meant for bookkeeping changes which do not introduce a discontinuity. If several affects fire at the same time, one asking for the reset is enough; parameter changes are saved either way. - Batched affects fill their observed buffer once per event instead of once per member.
save_parameters!no longer stores a full copy of the parameter vector. The solution keeps the initial parameters plus a list of the values which changed, which makes frequent parameter changes in large networks much cheaper in memory.- Observed functions only evaluate what is needed. MTK components now know which observables
depend on which, and which of them read the component inputs.
SII.observedonly evaluates the observables that were asked for and what they depend on. More importantly, it only runs the full network RHS to fill the input buffers if a requested observable actually reads an input. This mostly helps callbacks, which may read an observable at every interpolation point. - Aliases of states and parameters resolve directly to their slot. If
terminal.u ~ ufor a stateu, readingterminal.uno longer goes through the observed function. The alias map is now a regular field of the component models, set with thealiasmapconstructor keyword;set_aliasmap!,delete_aliasmap!andhas_aliasmapare removed. - improvements to the simplification pipeline around removing algebraic states
- Outputs fed forward from an input become algebraic states. When a vertex output depends
algebraically on the input, the ND-native MTK simplification now keeps that output as state
and the input equation as residual, e.g. the bus voltage and the current balance. The
observables of such components no longer read the input, so callback conditions on them
don't need the network buffers. The algebraic states of MTK components may change, e.g. PSS/E
machines now keep
busbar₊u_r,busbar₊u_iinstead of the stator currents. If making the outputs states would cost more states than the old tear, the old tear is kept. - Parallel edges. Several edge models may now connect the same pair of vertices. The
graphless constructor builds a
NetworkDynamics.ComponentGraphin that case and keeps the edge models in input order. Inputs without parallel edges still produce aSimpleGraphorSimpleDiGraphas before, unlesslegacy_graph=falseis passed.EIndex(src => dst)throws if it matches more than one edge. chk_componentreports allocations in f or g function of modelchk_component(c; ad=true)also calls f and g with ForwardDiff Duals and reports if they error or allocate only for Duals. Construction runs the check without this Dual pass.- New
chk_network(nw)checks the network rhs for allocations with Float64 and Duals and lists the component batches which allocate. - Fix: calling the network with Duals allocated on every call, due to the unspecialized element type in the cache getters.
ODEProblem(nw, ...)usesFullSpecializeby default. SciML'sAutoSpecializelimits ForwardDiff to chunk size 1, so a sparse Jacobian took one RHS pass per color. On IEEE39 the solve gets about 40 % faster.specialize=SciMLBase.AutoSpecializerestores the old behavior.- A new network reuses the compiled solver. The
Networktype no longer carries the component functions; the RHS reaches them through a function barrier (30–40 ns per call). On IEEE39 the first solve of a second network dropped from 17 s to 0.4 s. Reuse needs the same graph type, mass matrix type and float type.Network(...; fullytyped=true)restores the fully typed network. - DAE (re)initialization starts with Newton also without a
jac_prototype. The defaultinitializealgofODEProblem(nw, ...)now always uses the Jacobian-based polyalg (NewtonRaphson, then TrustRegion and LevenbergMarquardt) instead of the upstream default, which opens with Broyden. After an event Broyden could take hundreds of iterations and, on last-bit floating point differences, land on a different root than Newton. The internaldefault_dae_init_algis removed. OrdinaryDiffEqNonlinearSolveis now a weak dependency, only to require at least v2.9.4 whenever it is loaded. Older versions broke ForwardDiff in the DAE initialization of networks with ajac_prototype.- Fix: MTK models may carry several differing
initffor one target (e.g. two weak, optional recipes recovering a state from either its input or its output). Previously this errored atVertexModelconstruction; now initialization decides which one fires and reports a disagreement. A weakinitfis also no longer dropped next to an optional strong one. - Networks without edges can be built by passing an empty edge list, e.g.
Network(g, vm, [])orNetwork(vms, []). - Faster MTK components. Integer powers like
x^2in generated code now go throughBase.literal_pow(plain multiplication) instead of the generic, much slower integer power. MTK edges also write their src and dst outputs through one contiguous view. Together this made the RHS of a large PowerDynamics network about 4× faster, with identical results.
- Initialization values now travel across a two-term algebraic constraint. An equation
0 ~ a*x + b*ywith numeric coefficients determines either symbol from the other, so both directions join the resolution graph next to the observed and output equations — this is how a provided interface current reaches an injector through the KCL of a bus. Wider constraints are left alone. Where such a rule determines a settable state, that state is no longer handed to the nonlinear solver. find_fixpointaccepts a solve that stalls just short of convergence. NonlinearSolve terminates at≈3e-13, tight enough that a larger network can miss it on cancellation noise and reportStalledwith a perfectly good residual. A non-success return code is now only an error if the residual also misses the newtol=1e-10keyword. The solver's own tolerances are untouched, so it still converges as far as it can.find_fixpointand DAE initialization now select a sparsity-aware nonlinear solver when the network carries ajac_prototype, viaNetworkDynamics.default_fixpoint_algandNetworkDynamics.default_dae_init_alg;find_fixpoint'salgdefaults tonothingmeaning "let NetworkDynamics decide", and an explicitalgstill overrides. This also makesSparseMatrixColoringsa dependency, since NonlinearSolve only enables sparse-AD coloring when it is loaded somewhere in the process.set_jac_prototype!now stores a pattern with a full diagonal, whether it computed the pattern itself or was handed one. Solvers size the iteration matrixW = M/γ - Jfrom the prototype, andWhas a diagonal whatever the mass matrix looks like. Without those entries the sparseWsilently grows on first use, which breaks a GPU sparse solver that has already factorized the prototype symbolically.get_jac_prototypestill reports the detected pattern unchanged, structural zeros on the diagonal included.SparseConnectivityTraceris now a regular dependency instead of a weak one, and the sparsity detection moved fromext/NetworkDynamicsSparsityExt.jlintosrc/sparsity.jl.get_jac_prototypeandset_jac_prototype!work without loading anything extra.Network(...; sparse=true)detects the Jacobian sparsity pattern and stores it right away, equivalent to callingset_jac_prototype!on the finished network. The default staysfalse; a future:automay change that.- Nested conditionals no longer defeat the sparsity detection. The if/else rewrite works on
conditionals in value position and runs bottom-up, so nested
ifelseandelseifchains collapse instead of pushing the whole component onto the dense fallback.
Initialization-time formula resolution is now a single dependency graph
(#387). Init formulas, guess
formulas, observed equations and output equations all describe the same thing — out = f(in…) —
so they go into one bucket of rules and the execution order falls out of which symbols are
already known. This replaces the old approach of expanding observables into the formulas that
read them.
- Optional
InitFormulas (kwargoptional=true,set_initf(…; optional=true), or the[initf_optional = <expr>]variable option): a formula whose inputs never become known is skipped instead of failing the initialization. Whereweakyields on the target (a value is already there),optionalyields on the inputs. The two are independent and combinable. - An observable reached through a scaled alias (
y ~ -x) is now resolved by the graph in both directions instead of being folded into the aliasmap, so which way the scaling applies is decided per query. A value written onystill reaches the statex, on the init path and on theNWStatepath alike. boundswritten on a scaled alias no longer move onto the underlying state. That is a theoretical breakage from v1.1 but i am sure nothing depends on it.
Cross-component initialization metadata for per-unit / base-value handling: three separate features that resolve at three different times.
- Weak
InitFormulas: a formula declaredweak(kwargweak=true, macro@initformula weak=true …,set_initf(…; weak=true), or the[initf_weak = <expr>]variable option) yields to a value the user already set — it is dropped at init when its single target already carries adefaultor is written by a strong formula. This is the right precedence for defaulting (a value that follows another unless pinned), the inverse of a plaininitf, which always overwrites. bound_toparameter metadata:@parameters S_b [bound_to = :busbar₊S_b]declares a parameter as a structural alias of another symbol in the same component. It is realized as a real MTK binding before compilation, so the bound parameter leavespsymand reappears as an observable of its target — one true parameter for the quantity, nothing can desync. An explicit default on a bound parameter, or an unresolvable target, is an error.default_fromparameter metadata:@parameters S_b [default_from = (:src, :busbar₊S_b)]weakly copies a parameter's default from a neighboring component — the src/dst vertex of an edge (:src/:dst), or the hub an injector node hangs off (:hub). The value is resolved at network init and baked into a weakInitFormula, so it follows the source but stays independently settable (contrastbound_to, which eliminates the parameter). Unlikebound_to, the source lives in a different component, resolvable only once the graph exists.
Three themes: ModelingToolkit v11 support (which drops the AGPL dependency), a reworked initialization pipeline, and the SciML v3 stack.
ModelingToolkitv10 → v11 (#344). MTK v11 split intoModelingToolkitBase(MIT) andModelingToolkit(AGPL); NetworkDynamics now only depends on the MIT half, so the AGPL dependency is gone. (This does not make the package copyleft-free: theSparseArraysstdlib still pulls in SuiteSparse, whose UMFPACK/CHOLMOD/SPQR binaries are GPL-2.0-or-later. That is the weaker, non-network-clause copyleft that large parts of the Julia ecosystem already carry.) Along with it:SymbolicUtils≥4,Symbolics≥7.- MTK models are no longer simplified by
mtkcompile(#344). A built-in pipeline (alias/linear-state elimination, algebraic and nonlinear loop breaking, simple DAE index reduction) took its place, sincemtkcompilelives in the AGPL half. Passmtkcompile=truefor the old behavior,mtkcompile=:compareto print both side by side, or set the global default withNetworkDynamics.set_mtkcompile!. Models containing discrete variables now warn (still unsupported). - Symbolic expressions as a
guess, or bound to an unknown, now error (#378) (the@variables x(t) = <symbolic expr> [guess=<symbolic expr>]pattern). They used to be substituted once, at build time, and frozen into a number — so they silently went stale when the values they referenced changed, and an expression that could not be resolved was quietly dropped, leaving the variable free. Since formulas are now tracked properly, write@variables x(t) [initf = <expr>](or[guessf = <expr>]) instead; the error message names the rewrite. Symbolic values on@parametersdo not error: since MTKv11 those create a so-called parameter binding, the bound parameter on the lhs is moved to observed thus a permanent runtime dependency is injected. This is in contrast toinitf, which sets the parameter's numeric value once at init time. set_mtk_defaults!→set_mtk_defaults, non-mutating (#378): rebind the result,sys = set_mtk_defaults(sys, ...). A symbolic value now becomes a parameter binding (as if written@parameters K = K_e) rather than a default; numeric values are unchanged.SciMLBasev3 /OrdinaryDiffEqv7 (#374). Upstream changed the defaultinitializealgto check inconsistent initial conditions rather than reinitialize them, soODEProblem(nw, ...)now passesinitializealg=BrownFullBasicInit()to keep the previous behavior. Override withinitializealg=.... We deliberately chose to deviate from the DiffEq default here because for the kind of systems simulated with ND you mostly want DAE reinit at events and sim start.VectorContinuousComponentCallbacklostaffect_neg!(#374), matching DiffEq'sVectorContinuousCallback. The affect now receives anevent_signsvector (per output:+1upcrossing,-1downcrossing,0none) and resolves the direction itself.find_fixpointtakes anNWState(#344). TheVector,NWParameterand(NWState, NWParameter)forms are deprecated but still work.
- Aliased names are interchangeable
(#378): it no longer
matters which of several aliased symbols you attach metadata to.
:busbar.uand:terminal.uare the same state, so defaults, guesses, bounds and formulas written against either are routed to the canonical one. - Backward-flow initialization
(#378): an
InitFormulamay now write an observable, "pinning" it as a value downstream formulas read as input. This lets you write purely component-local formulas — a parent states what a child's output must be, the child's formula inverts its own equation — and have them chain end to end, the way power system models are usually initialized.GuessFormulapins are hints: they seed the solver but are never consistency-checked, so an entire backward chain can be spelled as guesses. - New
initf/guessfmetadata (#378):@variables x(t) [initf = <expr>]declares an initialization equation,[guessf = <expr>]a guess. Both also work on@parameters, and there they do something a binding cannot: the parameter stays a real, free parameter that the dynamics can use, it is merely given its value at initialization. (A binding would eliminate it into an observed equation instead.) That is what a setpoint back-computed from the operating point needs. A scalarguess=0and aguessf=<expr>can now coexist on one variable.set_initf(sys, target => expr, ...)andset_guessfattach the same thing at system level, for targets inside a subsystem (non-mutating, rebind the result). - More robust on badly scaled models
(#344,
#378): a failed init
solve is retried on a rescaled problem, and the
tol/nwtolresidual check falls back to a Jacobian-scaled residual before giving up. Stiff equations — e.g. a shunt capacitor'sDt(V_C) = (ω0/C)·Δi— no longer fail on a roundoff-level mismatch. The fallback can only relax the check, never tighten it. NWStategainsguess,apply_formulasandverbosekeywords. Withdefault=truevalues are filled in order: defaults/inits →InitFormulas → guesses (ifguess=true) →GuessFormulas.find_fixpoint's default start state now usesguess=true.initialize_component/initialize_componentwisegainwarn=falseto silence initialization warnings.
- New
copy(::Network)(#376), much cheaper thandeepcopy. doctornow smoketests each component's observable function.
- Open-loop linearization (#341):
- New
open_loop_linearization(s0)decomposes the network into open-loop subsystems (Ynw,Zbus,Yinj) for bus/injector node analysis - New
linearize_componentfor linearizing individual vertex/edge models in isolation - LTI algebra on
NetworkDescriptorSystem:append,feedback,*(series/gain/matrix),+(parallel),-(subtraction/negation) - Injector node helpers:
injector_vidxs,is_injector,has_injector_nodes - Type parameter change (soft-breaking):
NetworkDescriptorSystemnow has 8 type parameters (addedSTforsym) instead of 7
- New
- #342 default initialization problem solver chooses compatible
autodiff_vjpnow to prevent Enzyme errors in some nonlinear solve algorithms
- Linear analysis overhaul (#340): Renamed
linear_stability.jltolinear_analysis.jland significantly expanded linear analysis capabilities:- New
NetworkDescriptorSystemtype for descriptor system representation (M ẋ = Ax + Bu,y = Cx + Du) with callable transfer function evaluation - New
linearize_network(s0; in, out)for full ABCD state-space linearization with perturbation channel classification (vertex/edge inputs/outputs, parameters) - New
reduce_daeto eliminate algebraic constraints from descriptor systems - New
participation_factorsandshow_participation_factorsfor eigenmode participation analysis - New
eigenvalue_sensitivityandshow_eigenvalue_sensitivityfor parameter sensitivity of eigenvalues via nested ForwardDiff - API change (soft-breaking):
isfixpoint,jacobian_eigenvals,is_linear_stablenow takes0::NWStatedirectly instead of(nw, s0). Old signatures are deprecated with warnings.
- New
- New
set_mtk_defaults!helper for forwarding keyword defaults in@componentdefinitions initialize_component: addalg_kwargskeyword, defaultautodiff=AutoForwardDiff()in solver- New dependency:
ADTypes Base.copymethods forNWStateandNWParameterNWStateconstructor now validatesuflatlength- PrettyTables v3 compatibility for benchmark code
- Coreloop: added
perturb/perturb_mapskwargs for perturbation-based linearization
- small fixes, mainly for PowerDynamics tests (#339)
- Add
assume_io_couplingparameter to MTKVertexModelandEdgeModelconstructors (#332)- New optional parameter forces MTK to consider direct dependency chains from outputs to inputs
- Helps resolve cases where MTK simplification results in derivatives of input variables
- Improved error handling for RHS differentials with new
RHSDifferentialsErrorexception type that provides helpful guidance - fix performance bottleneck in MTK model "compilation"
- much improved sparsity tracing #334: no more manual dense/replaced_conditions keywords. Algorithm goes through network batch by batch (not component by component) and replaced incompatible component functions with fixed RGF or dense equivalent automatically.
- Add
LoopbackConnectionedge model for injector node pattern (#334)- New special edge type enables direct connection of "injector nodes" (vertices with flipped input-output scheme) to hub nodes
- Injector nodes take potential as input and output flow, allowing modular decomposition of complex vertex models
- Particularly useful for large networks where splitting vertex models into smaller components improves performance and reduces compilation time
- Add experimental
with_mtk_model_cachefunction (#334) to prevent repeated simplification and code gen for identical models.
Multiple new features from #331:
- Parallel component initialization: Add experimental
parallel=falsekeyword toinitialize_componentwisefor multithreaded component initialization with visual progress indicators - Better initialization defaults: Change default solver to
FastShortcutNLLSPolyalg(linsolve=QRFactorization())for better handling of ill-conditioned initialization problems - Handle duplicate symbols: Support initialization of components with duplicate state/output symbols (shadowing), with automatic validation that duplicates resolve to same values
- GPU compatibility for MTK models: MTK-generated models can now run on GPU via enhanced CUDA extension with proper handling of RuntimeGeneratedFunctions and function wrappers
- Network copy constructor enhancement:
Network(nw)now preserves JAC prototype when network structure is unchanged, improving performance for repeated network construction - Callback improvements: Support passing vectors/tuples of callbacks for a single component in
wrap_component_callbacks
Implemented in #326:
- Add
ComponentPostprocessingmetadata mechanism for MTK models to attach postprocessing functions (like callbacks) at subcomponent level - Enhance initialization system:
- Add
algandsolve_kwargsparameters toinitialize_componentandinitialize_componentwisefor better control over nonlinear solvers - Deprecate passing raw
kwargsto initialization functions - usealgandsolve_kwargsinstead (old behavior still works with warning) - Support passing solver options as dictionaries mapping
VIndex/EIndexto component-specific settings - Better error reporting for duplicate edge graphelements with detailed information about which edges conflict
- Add warning when MTK models use vector variables/parameters (unsupported feature)
- Add
- #324: Add custom ODEProblem constructor which takes a
NWStateobject rather than flat arrays. Also always generate Network callbacks automatically. If you've previously passedcallback=get_callbacks(nw), you'll get a deprecation note. For any other usage of thecallbackkeyword on ODEProblem Constructor you'll get an error.
- #323 Add
GuessFormulasystem for improving initial guesses in component initialization:- New
GuessFormulatype and@guessformulamacro for defining guess refinement formulas - GuessFormulas operate after InitFormulas in the initialization pipeline
- Unlike InitFormulas (which set defaults), GuessFormulas refine initial guesses for free variables
- Add
has_guessformula,get_guessformulas,set_guessformula!,add_guessformula!,delete_guessformulas!metadata functions - Add
additional_guessformulakeyword toinitialize_component,initialize_component!, andinitialize_componentwisefunctions - Improved initialization documentation with execution order details
- New
- #317 Enhanced callback system with negative affect support and runtime callback injection:
- Add
affect_neg!parameter toContinuousComponentCallbackandVectorContinuousComponentCallbackfor handling downcrossing events - Add ability to inject additional callbacks at runtime via
get_callbacks(nw, additional_callbacks)without storing them in component metadata - Add
NetworkDynamics.pretty_f()debugging utility for pretty-printing MTK-generated functions - Improve MTK integration warnings for nested event systems
- Better initialization error messages with specific variable information when NaN values are detected
- Add
- #314 Consolidate deprecated functionality: all deprecated functionality moved to dedicated
src/deprecated.jlfile for easier maintenance
- #313 Fix FilteringProxy issues and improvements:
- Fix bug with empty
getindexoperations on FilteringProxy - Add pattern highlighting in FilteringProxy display (matches shown in light red)
- Fix method ambiguity issues in symbolic indexing
- Improve callback error handling with early validation for wrong signatures
- Allow name clash reconstruction when it was previously allowed
- Fix bug with empty
- #312 Major rework of symbolic indexing system:
- New index types: Added
ParamIdxandStateIdxfor explicit parameter vs state numeric indexing - Enhanced proxy system: Replaced internal
VProxy/EProxywith newFilteringProxysystem that provides more powerful interactive filtering and inspection capabilities - Improved display system: Major enhancements to
showmethods with compact printing and matched name highlighting - New exports:
ParamIdx,StateIdx,generate_indices,FilteringProxy - All user-facing API remains backward compatible -
.vand.eproperties work as before but are now more powerful
- New index types: Added
- #309 improve error handling in initialization system:
- Add custom exception types:
NetworkInitErrorandComponentInitErrorwith detailed error messages - Enhanced error detection for NaN values, time-dependent systems, and RHS evaluation failures
- Improved
find_fixpointfunction with better input validation and time handling support (fixes #308) - Add equality (
==) and approximate equality (isapprox) methods forNWStateandNWParameter - Minor documentation fixes and spelling corrections
- fix #310 (allow
VPIndex(i)to index into network objects) - implement #307, allow nw[VIndex(:)], nw[[VIndex(1),VIndex(2)]]
- Add custom exception types:
- #303 update for ModelingToolkit.jl v10 compatibility:
- rename all
ODESystem->System(follows MTK v10 API) - MTK extension now uses
mtkcompileinstead ofstructural_simplifyinternally - Add new
implicit_outputfunction to handle fully implicit output variables in MTK models - Add documentation for handling fully implicit outputs in MTK integration
- Update minimum ModelingToolkit.jl requirement from v9.67 to v10
- rename all
- #301 improve callback system performance and flexibility:
- Add callback batching for better DiscreteComponentCallback performance
- Allow
EIndex(1=>2)as standalone edge index with relaxed type constraints - Optimize CallbackSet construction to prevent performance bottlenecks
- Add important documentation warning about parameter array copying in callbacks
- Fixed spelling:
ContinousComponentCallback→ContinuousComponentCallbackandVectorContinousComponentCallback→VectorContinuousComponentCallback(old names maintained as deprecated aliases for backward compatibility)
- #299 enhance metadata system with pattern matching and utility functions:
- Add String/Regex pattern matching for all metadata functions (
has_metadata,get_metadata,set_metadata!, etc.) - Add
strip_*!functions to remove all metadata of a specific type from components - Add
free_u()andfree_p()functions to identify variables/parameters without default values - Support removing metadata by passing
nothingormissingtoset_*!functions
- Add String/Regex pattern matching for all metadata functions (
- #294 add linear stability analysis functions:
isfixpoint,jacobian_eigenvals, andis_linear_stablewith support for both ODE and DAE systems - #283 add automatic sparsity detection using
get_jac_prototypeandset_jac_prototype! - #285 rename
delete_initconstraint!->delete_initconstaints!anddelete_initformula!->delete_initformulas!
-
BREAKING: the interface initialization of components has changed: it is now split up in two versions, mutating and non mutating version. Also it errors now if the tolerance bounds are violated. See docs on initialization for more details.
-
new
get_graph(::Network)method to extract graph object from nw -
improved Initialization System: Added comprehensive initialization formulas and constraints system:
- added
@initformulato add explicit algebraic init equations for specific variables - added
@initconstraintto add additional constraints for the component initialization
- added
-
allow access edges via Pairs, i.e.
EIndex(1=>2,:a)references variable:ain edge from vertex 1 to 2. Works also with unique names of vertices likeEIndex(:a=>:b)#281.
NetworkDynamics v0.9 is a complete overhaul of the previous releases of NetworkDynamics. Users of the package should probably read the new documentation carefully.
The most important changes are:
- Explicit split in
fandgfunction: There is no split intoODEandStaticcomponents anymore, everything is unified in component models with internal functionfand output functiong. - Parameters handling: Parameters are allways stored in a flat array. The Symbolic Indexing Interfaces helps to set and retrieve parameters.
- Automatic aggregation: vertices no longer receive a list of all connected
edges. This lead to inhomogeneous call signatures and was a performance
bottleneck. Now, each
Networkhas aaggregationfunction attached to it. The backaned will perform a reduction over all connected edges to calculate the input for a certain vertex. In practice, for typical flow networks you'll allways receive the sum of all flows rather than the individual flows. - Symbolic Indexing: the order of the states in the state vector changed in
non-trivial ways. But the old
idx_containingandsyms_containingfunctions have been replaced with a much more capable symbolic indexing framework.
- We dropped support for delay differential equations. If you've been using that feature please reach out to us.
- Due to the built aggregation, the vertices cannot explicitly handle the inputs from edges differently anymore. If you've been relying on those features reach out to us.