Skip to content

Commit f3b040d

Browse files
Merge pull request #2809 from AayushSabharwal/as/linearization-initsys-missing-vars
fix: fix unknowns not present in initialization system in linearization
2 parents c421522 + 3e81ea4 commit f3b040d

File tree

3 files changed

+145
-18
lines changed

3 files changed

+145
-18
lines changed

src/systems/abstractsystem.jl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,6 +1896,7 @@ function linearization_function(sys::AbstractSystem, inputs,
18961896
zero_dummy_der = false,
18971897
initialization_solver_alg = TrustRegion(),
18981898
eval_expression = false, eval_module = @__MODULE__,
1899+
warn_initialize_determined = true,
18991900
kwargs...)
19001901
inputs isa AbstractVector || (inputs = [inputs])
19011902
outputs isa AbstractVector || (outputs = [outputs])
@@ -1909,10 +1910,26 @@ function linearization_function(sys::AbstractSystem, inputs,
19091910
op = merge(defs, op)
19101911
end
19111912
sys = ssys
1913+
u0map = Dict(k => v for (k, v) in op if is_variable(ssys, k))
19121914
initsys = structural_simplify(
19131915
generate_initializesystem(
1914-
sys, guesses = guesses(sys), algebraic_only = true),
1916+
sys, u0map = u0map, guesses = guesses(sys), algebraic_only = true),
19151917
fully_determined = false)
1918+
1919+
# HACK: some unknowns may not be involved in any initialization equations, and are
1920+
# thus removed from the system during `structural_simplify`.
1921+
# This causes `getu(initsys, unknowns(sys))` to fail, so we add them back as parameters
1922+
# for now.
1923+
missing_unknowns = setdiff(unknowns(sys), all_symbols(initsys))
1924+
if !isempty(missing_unknowns)
1925+
if warn_initialize_determined
1926+
@warn "Initialization system is underdetermined. No equations for $(missing_unknowns). Initialization will default to using least squares. To suppress this warning pass warn_initialize_determined = false."
1927+
end
1928+
new_parameters = [parameters(initsys); missing_unknowns]
1929+
@set! initsys.ps = new_parameters
1930+
initsys = complete(initsys)
1931+
end
1932+
19161933
if p isa SciMLBase.NullParameters
19171934
p = Dict()
19181935
else
@@ -1983,7 +2000,9 @@ function linearization_function(sys::AbstractSystem, inputs,
19832000
p = todict(p)
19842001
newps = deepcopy(sys_ps)
19852002
for (k, v) in p
1986-
setp(sys, k)(newps, v)
2003+
if is_parameter(sys, k)
2004+
setp(sys, k)(newps, v)
2005+
end
19872006
end
19882007
p = newps
19892008
end

src/systems/nonlinear/initializesystem.jl

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,23 @@ function generate_initializesystem(sys::ODESystem;
7070
defs = merge(defaults(sys), filtered_u0)
7171
guesses = merge(get_guesses(sys), todict(guesses), dd_guess)
7272

73-
if !algebraic_only
74-
for st in full_states
75-
if st keys(defs)
76-
def = defs[st]
73+
for st in full_states
74+
if st keys(defs)
75+
def = defs[st]
7776

78-
if def isa Equation
79-
st keys(guesses) && check_defguess &&
80-
error("Invalid setup: unknown $(st) has an initial condition equation with no guess.")
81-
push!(eqs_ics, def)
82-
push!(u0, st => guesses[st])
83-
else
84-
push!(eqs_ics, st ~ def)
85-
push!(u0, st => def)
86-
end
87-
elseif st keys(guesses)
77+
if def isa Equation
78+
st keys(guesses) && check_defguess &&
79+
error("Invalid setup: unknown $(st) has an initial condition equation with no guess.")
80+
push!(eqs_ics, def)
8881
push!(u0, st => guesses[st])
89-
elseif check_defguess
90-
error("Invalid setup: unknown $(st) has no default value or initial guess")
82+
else
83+
push!(eqs_ics, st ~ def)
84+
push!(u0, st => def)
9185
end
86+
elseif st keys(guesses)
87+
push!(u0, st => guesses[st])
88+
elseif check_defguess
89+
error("Invalid setup: unknown $(st) has no default value or initial guess")
9290
end
9391
end
9492

test/downstream/linearize.jl

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,113 @@ lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2))
195195
@test isempty(lsys.B)
196196
@test isempty(lsys.C)
197197
@test lsys.D[] == 0
198+
199+
# Test case when unknowns in system do not have equations in initialization system
200+
using ModelingToolkit, OrdinaryDiffEq, LinearAlgebra
201+
using ModelingToolkitStandardLibrary.Mechanical.Rotational
202+
using ModelingToolkitStandardLibrary.Blocks: Add, Sine, PID, SecondOrder, Step, RealOutput
203+
using ModelingToolkit: connect
204+
205+
# Parameters
206+
m1 = 1
207+
m2 = 1
208+
k = 1000 # Spring stiffness
209+
c = 10 # Damping coefficient
210+
@named inertia1 = Inertia(; J = m1)
211+
@named inertia2 = Inertia(; J = m2)
212+
@named spring = Spring(; c = k)
213+
@named damper = Damper(; d = c)
214+
@named torque = Torque()
215+
216+
function SystemModel(u = nothing; name = :model)
217+
eqs = [connect(torque.flange, inertia1.flange_a)
218+
connect(inertia1.flange_b, spring.flange_a, damper.flange_a)
219+
connect(inertia2.flange_a, spring.flange_b, damper.flange_b)]
220+
if u !== nothing
221+
push!(eqs, connect(torque.tau, u.output))
222+
return ODESystem(eqs, t;
223+
systems = [
224+
torque,
225+
inertia1,
226+
inertia2,
227+
spring,
228+
damper,
229+
u
230+
],
231+
name)
232+
end
233+
ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name)
234+
end
235+
236+
@named r = Step(start_time = 0)
237+
model = SystemModel()
238+
@named pid = PID(k = 100, Ti = 0.5, Td = 1)
239+
@named filt = SecondOrder(d = 0.9, w = 10)
240+
@named sensor = AngleSensor()
241+
@named er = Add(k2 = -1)
242+
243+
connections = [connect(r.output, :r, filt.input)
244+
connect(filt.output, er.input1)
245+
connect(pid.ctr_output, :u, model.torque.tau)
246+
connect(model.inertia2.flange_b, sensor.flange)
247+
connect(sensor.phi, :y, er.input2)
248+
connect(er.output, :e, pid.err_input)]
249+
250+
closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, er],
251+
name = :closed_loop, defaults = [
252+
model.inertia1.phi => 0.0,
253+
model.inertia2.phi => 0.0,
254+
model.inertia1.w => 0.0,
255+
model.inertia2.w => 0.0,
256+
filt.x => 0.0,
257+
filt.xd => 0.0
258+
])
259+
260+
@test_nowarn linearize(closed_loop, :r, :y)
261+
262+
# https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3
263+
@mtkmodel Tank_noi begin
264+
# Model parameters
265+
@parameters begin
266+
ρ = 1, [description = "Liquid density"]
267+
A = 5, [description = "Cross sectional tank area"]
268+
K = 5, [description = "Effluent valve constant"]
269+
h_ς = 3, [description = "Scaling level in valve model"]
270+
end
271+
# Model variables, with initial values needed
272+
@variables begin
273+
m(t) = 1.5 * ρ * A, [description = "Liquid mass"]
274+
md_i(t), [description = "Influent mass flow rate"]
275+
md_e(t), [description = "Effluent mass flow rate"]
276+
V(t), [description = "Liquid volume"]
277+
h(t), [description = "level"]
278+
end
279+
# Providing model equations
280+
@equations begin
281+
D(m) ~ md_i - md_e
282+
m ~ ρ * V
283+
V ~ A * h
284+
md_e ~ K * sqrt(h / h_ς)
285+
end
286+
end
287+
288+
@named tank_noi = Tank_noi()
289+
@unpack md_i, h, m = tank_noi
290+
m_ss = 2.4000000003229878
291+
@test_nowarn linearize(tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2))
292+
293+
# Test initialization
294+
@variables x(t) y(t) u(t)=1.0
295+
@parameters p = 1.0
296+
eqs = [D(x) ~ p * u, x ~ y]
297+
@named sys = ODESystem(eqs, t)
298+
299+
matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0))
300+
matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0))
301+
@test matrices1 == matrices2
302+
303+
# Ensure parameter values passed as `Dict` are respected
304+
linfun, _ = linearization_function(sys, [u], []; op = Dict(x => 2.0))
305+
matrices = linfun([1.0], Dict(p => 3.0), 1.0)
306+
# this would be 1 if the parameter value isn't respected
307+
@test matrices.f_u[] == 3.0

0 commit comments

Comments
 (0)