Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
36a6a95
test: failing Q1 basic test for homotopy rewrite_trivial
BatchZero May 21, 2026
8c28ed9
feat: register homotopy operator + rewrite_trivial (L0 trivial form)
BatchZero May 21, 2026
2b7b770
refactor: align homotopy.jl with MTKBase style + tighten test
BatchZero May 21, 2026
86b613e
test: Q2 nested homotopy collapses to innermost actual
BatchZero May 21, 2026
414261c
test: Q3 homotopy preserved inside Base.ifelse branch structure
BatchZero May 21, 2026
20be7b0
test: Q4 vectorized homotopy via broadcast
BatchZero May 21, 2026
4bcc771
test: failing integration test for homotopy in InitializationProblem
BatchZero May 21, 2026
a47c6fe
test: refine RED integration test to ODESystem with algebraic homotopy
BatchZero May 21, 2026
3cca636
feat: rewrite homotopy to actual inside InitializationProblem
BatchZero May 21, 2026
743fd9e
test: register homotopy_init.jl in Initialization group
BatchZero May 21, 2026
e2b82a3
test: Buildings PressureDrop-style fixture exercises homotopy init
BatchZero May 21, 2026
fed7d60
feat: rewrite_with_lambda lowering for L1 sweep path
BatchZero May 24, 2026
2ef5255
feat: HomotopySweep, TrivialHomotopy, TrivialThenSweep algorithms
BatchZero May 24, 2026
aa9ce90
refactor: polish HomotopySweep — drop dead get_extension branch, doc …
BatchZero May 24, 2026
c63e47f
refactor: always lower homotopy(a,s) in init hook
BatchZero May 25, 2026
01e0262
feat: lower homotopy in complete() so parent and init share λ identity
BatchZero May 25, 2026
9441577
feat: stash setp handle + TrivialThenSweep default in OverrideInitData
BatchZero May 25, 2026
31943c7
feat: inject TrivialThenSweep as default initializealg in prob.kwargs
BatchZero May 26, 2026
648aa65
test: integration tests for default, explicit-sweep, explicit-trivial…
BatchZero May 26, 2026
4ff664a
test: OMC numerical parity for Buildings PressureDrop fixture
BatchZero May 26, 2026
f98b3be
test: end-to-end sweep-rescue from out-of-basin guess (TrivialThenSwe…
BatchZero Jun 1, 2026
6c5181a
fix: homotopy inner solves must not re-trigger DAE init
BatchZero Jun 1, 2026
4111bce
fix: default homotopy inner solver falls back to SimpleNonlinearSolve
BatchZero Jun 1, 2026
3d2d5ed
docs: document the Modelica homotopy operator (NEWS + API page)
BatchZero Jun 1, 2026
1bf8b4a
fix: auto-inject homotopy default initializealg for non-ODE problems
BatchZero Jun 1, 2026
f9838fb
refactor: provide homotopy default inner via NonlinearSolve extension
BatchZero Jun 1, 2026
a599a48
fix: reset homotopy λ to actual form on failed sweep step
BatchZero Jun 1, 2026
e1c85d2
docs: update homotopy operator docstring to reflect shipped sweep
BatchZero Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# ModelingToolkit v11 Release Notes

## Modelica `homotopy(actual, simplified)` operator

ModelingToolkit now supports Modelica's `homotopy(actual, simplified)` operator
(Modelica specification §3.7.4) as an initialization aid. Annotate a hard-to-solve
relation with an easy `simplified` form, and initialization can start from the
`simplified` problem and continuously deform it into the `actual` one. Each node is
lowered to `(1 - λ)*simplified + λ*actual` with a shared `__homotopy_λ` parameter;
for systems containing `homotopy` nodes the default initialization becomes
`OverrideInit(nlsolve = TrivialThenSweep(...))`, which tries the trivial solve at
`λ = 1` first and falls back to a `λ`-sweep continuation (mirroring OpenModelica).
`HomotopySweep`, `TrivialHomotopy` and `TrivialThenSweep` are exported for explicit
use via `initializealg`. See the "Homotopy continuation for initialization" API page.

## Symbolics@7 and SymbolicUtils@4 compatibility

SymbolicUtils version 4 involved a major overhaul of the core symbolic infrastructure, which
Expand Down
1 change: 1 addition & 0 deletions docs/pages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pages = [
"API/variables.md",
"API/model_building.md",
"API/problems.md",
"API/homotopy.md",
"API/dynamic_opt.md",
"API/codegen.md",
"API/PDESystem.md",
Expand Down
48 changes: 48 additions & 0 deletions docs/src/API/homotopy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
```@meta
CollapsedDocStrings = true
```

# Homotopy continuation for initialization (Modelica `homotopy`)

ModelingToolkit implements Modelica's `homotopy(actual, simplified)` operator
(Modelica specification §3.7.4). It is an initialization aid: where the `actual`
equations are hard to solve from the available guesses, the operator lets
initialization start from an easy `simplified` problem and continuously deform it
into the `actual` one.

During `complete`/`mtkcompile`, every `homotopy(actual, simplified)` node is lowered
to

```math
(1 - \lambda) \cdot \mathrm{simplified} + \lambda \cdot \mathrm{actual}
```

with a single shared parameter `__homotopy_λ` (default `1.0`). At `λ = 1` the system
reduces to `actual`; at `λ = 0` to `simplified`. For systems that contain `homotopy`
nodes, the default initialization algorithm becomes
`OverrideInit(nlsolve = TrivialThenSweep(...))`: it first attempts the trivial
single solve at `λ = 1` and, if that fails, runs a parameter-sweep continuation that
walks `λ` from 0 to 1 (mirroring OpenModelica's default user experience). Pass an
explicit `initializealg` to the problem constructor or to `solve` to override this.

At runtime, outside initialization, `homotopy(actual, simplified)` evaluates to
`actual`, as required by the specification.

!!! note
This operator is unrelated to the polynomial `HomotopyContinuationProblem`, which
solves polynomial systems via homotopy continuation. They only share the word
"homotopy".

## Operator

```@docs
ModelingToolkit.homotopy
```

## Initialization algorithms

```@docs
ModelingToolkit.TrivialThenSweep
ModelingToolkit.HomotopySweep
ModelingToolkit.TrivialHomotopy
```
3 changes: 2 additions & 1 deletion lib/ModelingToolkitBase/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
Pyomo = "0e8e1daf-01b5-4eba-a626-3897743a3816"
Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c"

Expand All @@ -86,6 +87,7 @@ MTKJuliaFormatterExt = "JuliaFormatter"
MTKLabelledArraysExt = "LabelledArrays"
MTKLatexifyExt = "Latexify"
MTKMooncakeExt = "Mooncake"
MTKNonlinearSolveExt = "NonlinearSolve"
MTKPyomoDynamicOptExt = "Pyomo"
MTKTrackerExt = "Tracker"

Expand Down Expand Up @@ -193,7 +195,6 @@ Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb"
OptimizationIpopt = "43fad042-7963-4b32-ab19-e2a4f9a67124"
Expand Down
18 changes: 18 additions & 0 deletions lib/ModelingToolkitBase/ext/MTKNonlinearSolveExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module MTKNonlinearSolveExt

using ModelingToolkitBase: ModelingToolkitBase
using NonlinearSolve: NewtonRaphson

# Make `NonlinearSolve.NewtonRaphson` the default inner solver for the homotopy
# continuation algorithms (`HomotopySweep`/`TrivialHomotopy`/`TrivialThenSweep`)
# whenever NonlinearSolve is loaded. NonlinearSolve is not a runtime dependency
# of ModelingToolkitBase, so `_default_inner` cannot reference it directly; this
# extension populates the factory Ref at load time. Without NonlinearSolve loaded
# ModelingToolkitBase falls back to `SimpleNonlinearSolve.SimpleNewtonRaphson`
# (a hard `[deps]`), so a `homotopy(...)` system is still buildable out of the box.
function __init__()
ModelingToolkitBase._DEFAULT_INNER_FACTORY[] = () -> NewtonRaphson()
return nothing
end

end
3 changes: 3 additions & 0 deletions lib/ModelingToolkitBase/src/ModelingToolkitBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ include("systems/ir_info.jl")
include("systems/codegen_utils.jl")
include("problems/docs.jl")
include("systems/codegen.jl")
include("systems/homotopy.jl")
include("systems/homotopy_sweep.jl")
include("systems/problem_utils.jl")

include("problems/compatibility.jl")
Expand Down Expand Up @@ -270,6 +272,7 @@ export liouville_transform, change_independent_variable,
export respecialize
export PDESystem
export Differential, expand_derivatives, @derivatives
export homotopy, HomotopySweep, TrivialHomotopy, TrivialThenSweep
export Equation
export Term
export SymScope, LocalScope, ParentScope, GlobalScope
Expand Down
5 changes: 4 additions & 1 deletion lib/ModelingToolkitBase/src/problems/daeproblem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ end

kwargs = process_kwargs(
sys; expression, callback, eval_expression, eval_module,
op, tspan, kwargs...
op, tspan,
initialization_data = (hasproperty(f, :initialization_data) ? f.initialization_data :
nothing),
kwargs...
)

diffvars = collect_differential_variables(sys)
Expand Down
34 changes: 34 additions & 0 deletions lib/ModelingToolkitBase/src/problems/initializationproblem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,40 @@ All other keyword arguments are forwarded to the wrapped problem constructor.
binds[get_iv(sys)::SymbolicT] = Symbolics.COMMON_ZERO
@set! isys.bindings = ROSymmapT(binds)
end
# Modelica `homotopy(actual, simplified)` (spec 3.7.4) lowering happens
# in the parent system inside `complete()` via `add_homotopy_parameter`
# (`systems/homotopy.jl`). The init system here inherits the lowered
# equations + `__homotopy_λ` parameter from the parent.
#
# `MTKParameters` requires every parameter to have a value in `op`
# (sys.bindings is intentionally NOT used — that would let `mtkcompile`
# substitute λ away and break the sweep path). So we plant the runtime
# default `λ = 1.0` into `op` here, which is op-level metadata and not
# consulted by `mtkcompile`'s substitution. `HomotopySweep` overrides
# this at solve time via the SII setp handle.
homotopy_λ_idx = findfirst(get_ps(isys)) do p
sym = unwrap(p)
SymbolicUtils.issym(sym) && nameof(sym) === :__homotopy_λ
end
if homotopy_λ_idx !== nothing
homotopy_λ_sym = unwrap(get_ps(isys)[homotopy_λ_idx])
if !haskey(op, homotopy_λ_sym)
op[homotopy_λ_sym] = 1.0
end
elseif has_homotopy_in_equations(equations(isys))
# Defensive fallback: caller skipped `complete()` (rare), do the
# lowering inline so `mtkcompile` doesn't see opaque homotopy nodes.
new_eqs, homotopy_λ = rewrite_with_lambda_in_equations(equations(isys))
@set! isys.eqs = new_eqs
homotopy_λ_sym = unwrap(homotopy_λ)
if !any(p -> isequal(p, homotopy_λ_sym), get_ps(isys))
@set! isys.ps = vcat(get_ps(isys), homotopy_λ_sym)
end
if !haskey(op, homotopy_λ_sym)
op[homotopy_λ_sym] = 1.0
end
end

if simplify_system
isys = mtkcompile(isys; fully_determined, split = is_split(sys), initsys_mtkcompile_kwargs...)
end
Expand Down
10 changes: 9 additions & 1 deletion lib/ModelingToolkitBase/src/problems/nonlinearproblem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ end
check_length, check_compatibility, expression, kwargs...
)

kwargs = process_kwargs(sys; kwargs...)
# Thread `initialization_data` so the homotopy default `initializealg`
# (OMC-aligned `OverrideInit(nlsolve = TrivialThenSweep)`) is auto-injected
# for homotopy systems lowered to a `NonlinearProblem` (PIPE-1).
kwargs = process_kwargs(
sys;
initialization_data = (hasproperty(f, :initialization_data) ? f.initialization_data :
nothing),
kwargs...
)
ptype = getmetadata(sys, ProblemTypeCtx, StandardNonlinearProblem())
args = (; f, u0, p, ptype)

Expand Down
11 changes: 9 additions & 2 deletions lib/ModelingToolkitBase/src/problems/odeproblem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ Base.@nospecializeinfer @fallback_iip_specialize function SciMLBase.ODEProblem{i
)

kwargs = process_kwargs(
sys; expression, callback, eval_expression, eval_module, op, _skip_events, tspan, kwargs...
sys; expression, callback, eval_expression, eval_module, op, _skip_events, tspan,
initialization_data = (hasproperty(f, :initialization_data) ? f.initialization_data : nothing),
kwargs...
)

ptype = getmetadata(sys, ProblemTypeCtx, StandardODEProblem())
Expand All @@ -139,7 +141,12 @@ end
is_steadystateprob = true, kwargs...
)

kwargs = process_kwargs(sys; expression, tspan = (0, Inf), kwargs...)
kwargs = process_kwargs(
sys; expression, tspan = (0, Inf),
initialization_data = (hasproperty(f, :initialization_data) ? f.initialization_data :
nothing),
kwargs...
)
args = (; f, u0, p)

maybe_codegen_scimlproblem(expression, SteadyStateProblem{_iip}, args; kwargs...)
Expand Down
5 changes: 4 additions & 1 deletion lib/ModelingToolkitBase/src/problems/sdeproblem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ end

kwargs = process_kwargs(
sys; expression, callback, eval_expression, eval_module,
op, _skip_events, tspan, kwargs...
op, _skip_events, tspan,
initialization_data = (hasproperty(f, :initialization_data) ? f.initialization_data :
nothing),
kwargs...
)

args = (; f, u0, tspan, p)
Expand Down
8 changes: 8 additions & 0 deletions lib/ModelingToolkitBase/src/systems/abstractsystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,14 @@ function complete(
_unhack_sys = unhack_system(sys)
if add_initial_parameters
sys = add_initialization_parameters(sys; split, _unhack_sys)::T
# Modelica `homotopy(actual, simplified)` operator (spec 3.7.4):
# if the system carries any homotopy nodes, lower them now to
# `(1-λ)*s + λ*a` and inject `__homotopy_λ` into the parent so
# `MTKParametersReconstructor` can resolve λ in init-system
# observed expressions. Lives at the same lifecycle point as
# `add_initialization_parameters` so the index_cache rebuild at
# line 723+ picks up λ uniformly.
sys = add_homotopy_parameter(sys)::T
end
cb_alg_eqs = Equation[alg_equations(_unhack_sys); observed(_unhack_sys)]
if has_continuous_events(sys) && is_time_dependent(sys)
Expand Down
Loading
Loading