Skip to content

Linearize each clock partition of a hybrid system separately - #5168

Closed
baggepinnen wants to merge 8 commits into
masterfrom
feat/clocked-linearization
Closed

baggepinnen wants to merge 8 commits into
masterfrom
feat/clocked-linearization

Conversation

@baggepinnen

@baggepinnen baggepinnen commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Closes part of #5166.

linearize builds its linearization from the continuous problem and has no representation for a
clocked subsystem. On a model whose controller is a synchronous program, the result is the plant
with the controller's contribution missing, and nothing says so.

This adds linearize_clocked, which splits the model the way mtkcompile! does, linearizes each
partition in its own time domain, and reports the signals that cross each clock boundary so that
the caller can reassemble the loop.

dt = 0.1
k = ShiftIndex(Clock(dt))
@variables x(t) y(t) u(t) yd(t) ud(t) r(t)
@parameters kp

@named sys = System(
    [
        yd ~ Sample(dt)(y)
        ud ~ ud(k - 1) + kp * (r - yd)
        u ~ Hold(ud)
        D(x) ~ -x + u
        y ~ x
    ], t)

controller, plant = linearize_clocked(sys, [r], [y]; op)
ClockPartition on SciMLBase.PeriodicClock(0.1, 0.0)
  state dimension 1, 2 inputs, 1 outputs
  inputs:
    requested: r(t)
    from partition 2: y(t)
  outputs:
    to partition 2: ud(t)

Each partition is returned as a ClockPartition carrying its clock, its matrices, and its input
and output signals grouped into the requested ones and one group per neighbouring partition. The
same variable names a signal on both sides of a boundary, so the partitions can be connected by
name. The continuous partition is returned in continuous time and every clocked partition in
discrete time at its own sample interval.

Why the partitions are returned separately

Discretizing the continuous partition at the clock interval and connecting it to the clocked
partitions is exact for a single periodic clock, since Hold is a zero-order hold and Sample is
ideal sampling. Converting the clocked partitions to continuous time instead keeps the resolution
of the fast dynamics, but it is an approximation that rests on the signals crossing each boundary
having little content above that boundary's Nyquist frequency — a property of the nonlinear model,
not of any linearization. Returning the partitions separately, each with its own rate, is what
keeps that distinction available to the caller; assembling the loop is left to the caller, and
there is no new dependency on ControlSystems.

Scope

Periodic clocks. A partition on any other clock is returned without matrices but with its signal
lists, so a model of it obtained by other means can be connected in its place, and a warning names
it. Events take no part in the result and are reported the same way. The Tc-integration approach
also described in the issue is not attempted here.

Implementation notes

The bulk of the work is naming. A signal crossing a boundary arrives as the Sample or Hold
term that carries it, and mark_discrete shifts a clocked partition's own variables forward one
step; both are operator expressions, which generate_initializesystem rejects as system inputs
and whose spelling reassembly does not preserve. The crossing terms are rewritten in the tearing
state before compilation, and what remains spelled shifted is renamed once the partition is
compiled, which leaves each partition an ordinary system addressed by the names the model uses.

linearization_function gains already_simplified, problem_constructor and problem_kwargs,
so that the discrete path reuses the existing jacobian machinery with a DiscreteProblem in place
of the ODEProblem. The assembly of the statespace matrices is factored out of
solve(::LinearizationProblem) into _linearization_matrices.

Two fixes fell out of a partition without state variables, which is the shape a clocked
proportional controller takes: the input count in the zero-unknown branch of
LinearizationFunction was read as length(::Int), and the output jacobian there was built with
the input count as its row dimension.

Tests

test/clock_linearization.jl, registered in the InterfaceII group. The matrices are checked
against the difference and differential equations the models were written as, and against the
closed loop assembled from the partitions: for a first-order plant under a clocked PI, the plant
discretized over one sample interval and closed with the controller reproduces the closed-loop
dynamics exactly. Also covered: a model without clocks agreeing with linearize, a clocked
partition without a state variable, two rates with a partition each, analysis points, an operating
point taken from a solution, the three warnings, and a clocked array variable used as a
whole at a previous tick.

A model containing a clock cannot be simulated by ModelingToolkit alone, so none of these compare
against a closed-loop simulation; the references are derived by hand.

Dependency

Compiling a partition whose clocked array variable is used as a whole one tick back produced a
reference to a variable the partition did not declare
(#5169), which is the shape a
model-predictive controller takes when its control law is called with the whole state estimate.
JuliaComputing/StateSelection.jl#161
shifts the unscalarized form along with its scalarized elements. The ModelingToolkitTearing
bound here is raised to 1.20.7, so this pull request needs the release carrying that fix before
it resolves.

Checklist

  • Appropriate tests were added
  • Any code changes were done in a way that does not break public API
  • All documentation related to code changes were updated
  • The new code follows the
    contributor guidelines

🤖 Generated with Claude Code

https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN

baggepinnen and others added 4 commits September 19, 2026 07:12
`linearize` builds its linearization from a continuous `ODEProblem` and has no representation
for a clocked subsystem. On a model whose controller is a synchronous program the result is
the plant with the controller's contribution missing, and nothing says so.

`linearize_clocked` splits the model the way the compiler does, linearizes each partition in
its own time domain, and reports the signals that cross each boundary so that the caller can
reassemble the loop. Each partition is returned as a `ClockPartition` carrying its clock, its
matrices, and its input and output signals grouped into the requested ones and one group per
neighbouring partition. The continuous partition is returned in continuous time and every
clocked partition in discrete time at its own sample interval, which keeps each boundary's
Nyquist frequency visible rather than hiding it behind a single rate.

`linearization_function` gains `already_simplified`, `problem_constructor` and
`problem_kwargs` so that the discrete path reuses the existing jacobian machinery with a
`DiscreteProblem` in place of the `ODEProblem`, and the assembly of the statespace matrices
is factored out of `solve(::LinearizationProblem)` into `_linearization_matrices`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
A signal crossing a clock boundary reaches a partition as the `Sample` or `Hold` term that
carries it, and inside a clocked partition every variable is shifted forward one step. Both
are operator expressions, which the compiler does not accept as system inputs and whose
spelling the reassembled system does not keep. Rewriting the crossing terms in the tearing
state, and renaming the remaining shifted parameters once the partition is compiled, leaves
each partition an ordinary system addressed by the names the model uses.

A clocked partition without a state variable is a static map whose matrices are the same in
either time domain, and `DiscreteProblem` cannot represent a system without unknowns, so it
goes through the continuous path; only its reported clock says which rate it runs at. That
case also exposed wrong shapes in the jacobians for a system without unknowns: the input
count was read as `length(::Int)` and the output jacobian was built with the input count for
its row dimension.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
The tests check the matrices against the difference and differential equations the models
were written as, and against the closed loop assembled from the partitions: for a first-order
plant under a clocked PI, the plant discretized over one sample interval and closed with the
controller reproduces the closed-loop dynamics exactly, which is what makes the boundary
bookkeeping worth trusting. Also covered: a model without clocks agreeing with `linearize`, a
clocked partition without a state variable, two rates with a partition each, analysis points,
an operating point taken from a solution, and the warnings for an event, for a clock that is
not periodic and for an operating-point entry that names nothing.

A model containing a clock cannot be simulated by ModelingToolkit alone, so none of these
compare against a closed-loop simulation; the references are derived by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
Comment on lines +147 to +148
println(io, " state dimension ", size(p.A, 1), ", ", length(p.inputs),
" inputs, ", length(p.outputs), " outputs")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
println(io, " state dimension ", size(p.A, 1), ", ", length(p.inputs),
" inputs, ", length(p.outputs), " outputs")
println(
io, " state dimension ", size(p.A, 1), ", ", length(p.inputs),
" inputs, ", length(p.outputs), " outputs"
)

println(io, " not linearized")
end
for (label, groups, vars) in (
("inputs", p.input_groups, p.inputs), ("outputs", p.output_groups, p.outputs))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
("inputs", p.input_groups, p.inputs), ("outputs", p.output_groups, p.outputs))
("inputs", p.input_groups, p.inputs), ("outputs", p.output_groups, p.outputs),
)

Comment on lines +157 to +158
origin = j == 0 ? "requested" : (label == "inputs" ? "from partition $j" :
"to partition $j")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
origin = j == 0 ? "requested" : (label == "inputs" ? "from partition $j" :
"to partition $j")
origin = j == 0 ? "requested" : (
label == "inputs" ? "from partition $j" :
"to partition $j"
)

Comment thread src/linearization.jl
linres = prob.f(u0, p, t)
f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u, x, p, t = linres
matrices = _linearization_matrices(
linres, inputs(prob.f.prob.f.sys); allow_input_derivatives)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
linres, inputs(prob.f.prob.f.sys); allow_input_derivatives)
linres, inputs(prob.f.prob.f.sys); allow_input_derivatives
)

using ModelingToolkit, Test, LinearAlgebra
using ModelingToolkit: t_nounits as t, D_nounits as D
using ModelingToolkit: AnalysisPoint, sampletime, is_continuous_partition, is_linearized,
input_group, output_group

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
input_group, output_group
input_group, output_group

u ~ Hold(ud),
D(x) ~ -x + u,
y ~ x,
], t)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
], t)
], t
)

], t)
op = Dict(x => 0.0, y => 0.0, u => 0.0, ud => 0.0, kp => 1.0, nothere => 3.0)
@test_warn ["nothere", "ignored"] linearize_clocked(
sys, ModelingToolkit.SymbolicT[], [y]; op)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
sys, ModelingToolkit.SymbolicT[], [y]; op)
sys, ModelingToolkit.SymbolicT[], [y]; op
)

u ~ Hold(ud),
D(x) ~ -x + u,
y ~ x,
], t)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
], t)
], t
)

Comment on lines +287 to +288
extra = Dict(y => sol(0.5; idxs = xp), u => 0.0, ud => 0.0,
ud(k - 1) => 0.0, kp => 2.0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
extra = Dict(y => sol(0.5; idxs = xp), u => 0.0, ud => 0.0,
ud(k - 1) => 0.0, kp => 2.0)
extra = Dict(
y => sol(0.5; idxs = xp), u => 0.0, ud => 0.0,
ud(k - 1) => 0.0, kp => 2.0
)

ud(k - 1) => 0.0, kp => 2.0)
partitions = linearize_clocked(
sys, ModelingToolkit.SymbolicT[], [y];
op = Dict(x => sol(0.5; idxs = xp), extra...))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
op = Dict(x => sol(0.5; idxs = xp), extra...))
op = Dict(x => sol(0.5; idxs = xp), extra...)
)

@baggepinnen
baggepinnen marked this pull request as draft September 19, 2026 07:24
@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia vlts)

Time benchmarks
master 2163c78... master / 2163c78...
ODEProblem 5.78 ± 0.11 ms 5.79 ± 0.14 ms 0.999 ± 0.031
init 0.116 ± 0.034 ms 0.0996 ± 0.034 ms 1.16 ± 0.52
large_parameter_init/ODEProblem 17.6 ± 1 ms 18.4 ± 0.96 ms 0.957 ± 0.076
large_parameter_init/init 0.104 ± 0.027 ms 0.103 ± 0.027 ms 1.01 ± 0.38
mtkcompile 10.1 ± 0.27 ms 10.4 ± 0.51 ms 0.977 ± 0.054
sparse_analytical_jacobian/ODEProblem 22.1 ± 1.6 ms 22.4 ± 1.1 ms 0.985 ± 0.086
sparse_analytical_jacobian/f_iip 0.07 ± 0.001 μs 0.07 ± 0 μs 1 ± 0.014
sparse_analytical_jacobian/f_oop 0.379 ± 0.011 ms 0.377 ± 0.011 ms 1 ± 0.042
time_to_load 6.7 ± 0.049 s 6.86 ± 0.034 s 0.978 ± 0.0086
Memory benchmarks
master 2163c78... master / 2163c78...
ODEProblem 0.0375 M allocs: 1.93 MB 0.0375 M allocs: 1.95 MB 0.992
init 0.5 k allocs: 0.0843 MB 0.5 k allocs: 0.0843 MB 1
large_parameter_init/ODEProblem 0.161 M allocs: 6.05 MB 0.161 M allocs: 6.07 MB 0.997
large_parameter_init/init 0.632 k allocs: 0.178 MB 0.632 k allocs: 0.178 MB 1
mtkcompile 0.0594 M allocs: 3.36 MB 0.0594 M allocs: 3.38 MB 0.995
sparse_analytical_jacobian/ODEProblem 0.118 M allocs: 5.74 MB 0.118 M allocs: 5.74 MB 1
sparse_analytical_jacobian/f_iip 0 allocs: 0 B 0 allocs: 0 B
sparse_analytical_jacobian/f_oop 0.634 k allocs: 19.6 kB 0.634 k allocs: 19.6 kB 1
time_to_load 0.153 k allocs: 14.5 kB 0.153 k allocs: 14.5 kB 1

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia v1)

Time benchmarks
master 2163c78... master / 2163c78...
ODEProblem 5.04 ± 0.071 ms 5.03 ± 0.11 ms 1 ± 0.026
init 0.119 ± 0.021 ms 0.118 ± 0.018 ms 1.01 ± 0.23
large_parameter_init/ODEProblem 13.6 ± 0.41 ms 13.7 ± 0.47 ms 0.989 ± 0.045
large_parameter_init/init 0.138 ± 0.035 ms 0.14 ± 0.028 ms 0.988 ± 0.32
mtkcompile 8.5 ± 0.77 ms 8.42 ± 0.7 ms 1.01 ± 0.12
sparse_analytical_jacobian/ODEProblem 17.8 ± 1.9 ms 18.4 ± 1.7 ms 0.965 ± 0.14
sparse_analytical_jacobian/f_iip 0.061 ± 0.01 μs 0.061 ± 0.01 μs 1 ± 0.23
sparse_analytical_jacobian/f_oop 0.106 ± 0.011 ms 0.106 ± 0.012 ms 1 ± 0.15
time_to_load 5.14 ± 0.025 s 5.11 ± 0.0061 s 1.01 ± 0.0051
Memory benchmarks
master 2163c78... master / 2163c78...
ODEProblem 0.0377 M allocs: 1.71 MB 0.0377 M allocs: 1.7 MB 1.01
init 0.472 k allocs: 0.0602 MB 0.472 k allocs: 0.0602 MB 1
large_parameter_init/ODEProblem 0.157 M allocs: 5.39 MB 0.157 M allocs: 5.39 MB 1
large_parameter_init/init 0.754 k allocs: 0.166 MB 0.754 k allocs: 0.166 MB 1
mtkcompile 0.0557 M allocs: 2.63 MB 0.0557 M allocs: 2.63 MB 1
sparse_analytical_jacobian/ODEProblem 0.117 M allocs: 4.76 MB 0.117 M allocs: 4.76 MB 1
sparse_analytical_jacobian/f_iip 0 allocs: 0 B 0 allocs: 0 B
sparse_analytical_jacobian/f_oop 0.848 k allocs: 27 kB 0.848 k allocs: 27 kB 1
time_to_load 0.201 k allocs: 11.8 kB 0.201 k allocs: 11.8 kB 1

…uses

Three corrections found by pointing the partition-wise linearization at a model whose controller
is a model-predictive controller with an array-valued state estimate.

An operating-point entry that names a state variable of a partition is now kept on the strength of
that alone. The partition a signal belongs to is still consulted, but only for an entry naming an
observed variable, which several partitions may observe. The membership test also looks through
`getindex`, so that an entry for one element of an array-valued state finds its way.

A clocked partition's inputs no longer carry the `missing` binding that `inputs_to_parameters!`
leaves for an initialization problem to resolve. Such a partition solves none, and the discrete
code generation substitutes bindings into the expressions it builds, so a `missing` reached them
and the symbolic type of a product could not be computed. Every input of a clocked partition is
required in the operating point instead, and says so if it is absent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
Comment on lines +189 to +191
SymbolicIndexingInterface.is_variable(psys, key) ||
(allow_observed && belongs(key) &&
SymbolicIndexingInterface.is_observed(psys, key))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
SymbolicIndexingInterface.is_variable(psys, key) ||
(allow_observed && belongs(key) &&
SymbolicIndexingInterface.is_observed(psys, key))
SymbolicIndexingInterface.is_variable(psys, key) ||
(
allow_observed && belongs(key) &&
SymbolicIndexingInterface.is_observed(psys, key)
)

return v
end
end
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
end
return
end


discrete = !(clock isa SciMLBase.ContinuousClock)
psys, stateless = _compile_partition(
ts, fullvars, in_vars, out_vars, crossing_in[i], iv, discrete)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
ts, fullvars, in_vars, out_vars, crossing_in[i], iv, discrete)
ts, fullvars, in_vars, out_vars, crossing_in[i], iv, discrete
)

return domain !== nothing && partition_of_clock[domain] == i
end
partition_op = _partition_op(
psys, op, belongs, seen_op_keys; allow_observed = !discrete)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
psys, op, belongs, seen_op_keys; allow_observed = !discrete)
psys, op, belongs, seen_op_keys; allow_observed = !discrete
)

end
linres = linfun(u0, parameter_values(linfun), t)
matrices = _linearization_matrices(
linres, in_vars; allow_input_derivatives)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
linres, in_vars; allow_input_derivatives)
linres, in_vars; allow_input_derivatives
)

# renamed back once the partition is compiled.
shifted_io = Dict{SymbolicT, SymbolicT}(
τ => v
for (v, τ) in Iterators.flatten((zip(in_vars, in_terms), zip(out_vars, out_terms)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
for (v, τ) in Iterators.flatten((zip(in_vars, in_terms), zip(out_vars, out_terms)))
for (v, τ) in Iterators.flatten((zip(in_vars, in_terms), zip(out_vars, out_terms)))

A clocked array variable whose elements are aliased to sampled inputs and which is
used as a whole one tick back lowered to a variable the compiled partition did not
declare, so code generation for that partition failed
(#5169). JuliaComputing/StateSelection.jl#161 shifts the
unscalarized form along with its scalarized elements.

Add the regression test that fix makes pass — the two spellings of the same control
law must give the same matrices — and raise the ModelingToolkitTearing bound to the
release that will carry it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
Comment on lines +325 to +326
op = Dict(x1 => 0.0, x2 => 0.0, y1 => 0.0, y2 => 0.0, u => 0.0, ud => 0.0,
xd(k - 1)[1] => 0.0, xd(k - 1)[2] => 0.0, kp => kpval)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
op = Dict(x1 => 0.0, x2 => 0.0, y1 => 0.0, y2 => 0.0, u => 0.0, ud => 0.0,
xd(k - 1)[1] => 0.0, xd(k - 1)[2] => 0.0, kp => kpval)
op = Dict(
x1 => 0.0, x2 => 0.0, y1 => 0.0, y2 => 0.0, u => 0.0, ud => 0.0,
xd(k - 1)[1] => 0.0, xd(k - 1)[2] => 0.0, kp => kpval
)

baggepinnen and others added 2 commits September 19, 2026 15:31
A parameter without a value is bound to `missing`, for an initialization problem to
solve for. A clocked partition solves none, and the discrete code generation
substitutes every binding, so a parameter whose binding depends on such a value
reached the generated expressions as a literal `missing` and code generation stopped
with `TypeError: expected DataType, got Type{Union{Missing, Real}}`.

Drop those bindings on a clocked partition. A parameter the partition's equations
reference then takes its value from the operating point; one they do not reference is
dropped outright, a partition carrying the whole model's parameter set so that most of
them belong to another partition's equations.

This is exercised by a heat-pump model whose plant leaves fifty parameters to
initialization, not by the test suite: the same substitution on the continuous
partition runs through `generate_initializesystem`, which the partition-wise path does
not currently handle, so a model small enough to be a test case fails there instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
`generate_rhs` adds the expression of every assertion to the right-hand side it
generates. A partition inherits the whole model's assertions, so one stating a bound
on a variable of another partition was generated as a reference to a name the
partition does not declare, and the generated function failed at its first call with
an `UndefVarError`. On a heat-pump model this was a range check on the weather
reader's dry-bulb temperature, whose bounds are parameters the partition carries and
whose subject is a variable of the continuous partition.

Keep the assertions every variable of which the partition declares and drop the rest.
A clocked partition names its own variables by their past values, which no assertion
of a model is written in terms of, so it keeps none; an assertion is a statement about
a simulated trajectory and takes no part in a linearization either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxXfPwLvBCg2oeW7jm6MfN
"""
function _unresolvable_bindings(binds::AbstractDict)
unresolved = Set{SymbolicT}(
par for (par, val) in binds if SU.symtype(val) === Missing)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
par for (par, val) in binds if SU.symtype(val) === Missing)
par for (par, val) in binds if SU.symtype(val) === Missing
)

end
grew || return unresolved
end
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
end
return
end

end
@set! psys.bindings = ROSymmapT(binds)
@set! psys.outputs = OrderedSet{SymbolicT}(
SymbolicT[get(subs, o, o) for o in get_outputs(psys)])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
SymbolicT[get(subs, o, o) for o in get_outputs(psys)])
SymbolicT[get(subs, o, o) for o in get_outputs(psys)]
)

Comment on lines +663 to +664
@set! sys.initialization_eqs = Equation[substitute(eq, subs)
for eq in get_initialization_eqs(sys)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
@set! sys.initialization_eqs = Equation[substitute(eq, subs)
for eq in get_initialization_eqs(sys)]
@set! sys.initialization_eqs = Equation[
substitute(eq, subs)
for eq in get_initialization_eqs(sys)
]

u ~ Hold(ud),
D(x) ~ -x + u,
y ~ x,
], t; assertions = Dict(x < 10.0 => "the plant state exceeded its bound"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Runic] reported by reviewdog 🐶

Suggested change
], t; assertions = Dict(x < 10.0 => "the plant state exceeded its bound"))
], t; assertions = Dict(x < 10.0 => "the plant state exceeded its bound")
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant