1- using ModelingToolkit, ModelingToolkitStandardLibrary
1+ using ModelingToolkit, ModelingToolkitStandardLibrary. Blocks
2+ using OrdinaryDiffEq, LinearAlgebra
23using Test
3- using ModelingToolkit: t_nounits as t, D_nounits as D
4+ using ModelingToolkit: t_nounits as t, D_nounits as D, AnalysisPoint, get_sensitivity,
5+ get_comp_sensitivity, get_looptransfer
6+ using Symbolics: NAMESPACE_SEPARATOR
47
58@testset " AnalysisPoint is lowered to `connect`" begin
69 @named P = FirstOrder (k = 1 , T = 1 )
710 @named C = Gain (; k = - 1 )
8-
11+
912 ap = AnalysisPoint (:plant_input )
1013 eqs = [connect (P. output, C. input)
1114 connect (C. output, ap, P. input)]
@@ -21,3 +24,236 @@ using ModelingToolkit: t_nounits as t, D_nounits as D
2124
2225 @test isequal (sys_ap2, sys_normal2)
2326end
27+
28+ # also tests `connect(input, name::Symbol, outputs...)` syntax
29+ @testset " AnalysisPoint is accessible via `getproperty`" begin
30+ @named P = FirstOrder (k = 1 , T = 1 )
31+ @named C = Gain (; k = - 1 )
32+
33+ eqs = [connect (P. output, C. input), connect (C. output, :plant_input , P. input)]
34+ sys_ap = ODESystem (eqs, t, systems = [P, C], name = :hej )
35+ ap2 = @test_nowarn sys_ap. plant_input
36+ @test nameof (ap2) == Symbol (join ([" hej" , " plant_input" ], NAMESPACE_SEPARATOR))
37+ @named sys = ODESystem (Equation[], t; systems = [sys_ap])
38+ ap3 = @test_nowarn sys. hej. plant_input
39+ @test nameof (ap3) == Symbol (join ([" sys" , " hej" , " plant_input" ], NAMESPACE_SEPARATOR))
40+ sys = complete (sys)
41+ ap4 = sys. hej. plant_input
42+ @test nameof (ap4) == Symbol (join ([" hej" , " plant_input" ], NAMESPACE_SEPARATOR))
43+ end
44+
45+ # ## Ported from MTKStdlib
46+
47+ @named P = FirstOrder (k = 1 , T = 1 )
48+ @named C = Gain (; k = - 1 )
49+
50+ ap = AnalysisPoint (:plant_input )
51+ eqs = [connect (P. output, C. input), connect (C. output, ap, P. input)]
52+ sys = ODESystem (eqs, t, systems = [P, C], name = :hej )
53+ @named nested_sys = ODESystem (Equation[], t; systems = [sys])
54+
55+ @testset " simplifies and solves" begin
56+ ssys = structural_simplify (sys)
57+ prob = ODEProblem (ssys, [P. x => 1 ], (0 , 10 ))
58+ sol = solve (prob, Rodas5 ())
59+ @test norm (sol. u[1 ]) >= 1
60+ @test norm (sol. u[end ]) < 1e-6 # This fails without the feedback through C
61+ end
62+
63+ @testset " get_sensitivity - $name " for (name, sys, ap) in [
64+ (" inner" , sys, sys. plant_input),
65+ (" nested" , nested_sys, nested_sys. hej. plant_input),
66+ (" inner - nonamespace" , sys, :plant_input ),
67+ (" inner - Symbol" , sys, nameof (sys. plant_input)),
68+ (" nested - Symbol" , nested_sys, nameof (nested_sys. hej. plant_input))
69+ ]
70+ matrices, _ = get_sensitivity (sys, ap)
71+ @test matrices. A[] == - 2
72+ @test matrices. B[] * matrices. C[] == - 1 # either one negative
73+ @test matrices. D[] == 1
74+ end
75+
76+ @testset " get_comp_sensitivity - $name " for (name, sys, ap) in [
77+ (" inner" , sys, sys. plant_input),
78+ (" nested" , nested_sys, nested_sys. hej. plant_input),
79+ (" inner - nonamespace" , sys, :plant_input ),
80+ (" inner - Symbol" , sys, nameof (sys. plant_input)),
81+ (" nested - Symbol" , nested_sys, nameof (nested_sys. hej. plant_input))
82+ ]
83+ matrices, _ = get_comp_sensitivity (sys, ap)
84+ @test matrices. A[] == - 2
85+ @test matrices. B[] * matrices. C[] == 1 # both positive or negative
86+ @test matrices. D[] == 0
87+ end
88+
89+ #=
90+ # Equivalent code using ControlSystems. This can be used to verify the expected results tested for above.
91+ using ControlSystemsBase
92+ P = tf(1.0, [1, 1])
93+ C = 1 # Negative feedback assumed in ControlSystems
94+ S = sensitivity(P, C) # or feedback(1, P*C)
95+ T = comp_sensitivity(P, C) # or feedback(P*C)
96+ =#
97+
98+ @testset " get_looptransfer - $name " for (name, sys, ap) in [
99+ (" inner" , sys, sys. plant_input),
100+ (" nested" , nested_sys, nested_sys. hej. plant_input),
101+ (" inner - nonamespace" , sys, :plant_input ),
102+ (" inner - Symbol" , sys, nameof (sys. plant_input)),
103+ (" nested - Symbol" , nested_sys, nameof (nested_sys. hej. plant_input))
104+ ]
105+ matrices, _ = get_looptransfer (sys, ap)
106+ @test matrices. A[] == - 1
107+ @test matrices. B[] * matrices. C[] == - 1 # either one negative
108+ @test matrices. D[] == 0
109+ end
110+
111+ #=
112+ # Equivalent code using ControlSystems. This can be used to verify the expected results tested for above.
113+ using ControlSystemsBase
114+ P = tf(1.0, [1, 1])
115+ C = -1
116+ L = P*C
117+ =#
118+
119+ @testset " open_loop - $name " for (name, sys, ap) in [
120+ (" inner" , sys, sys. plant_input),
121+ (" nested" , nested_sys, nested_sys. hej. plant_input),
122+ (" inner - nonamespace" , sys, :plant_input ),
123+ (" inner - Symbol" , sys, nameof (sys. plant_input)),
124+ (" nested - Symbol" , nested_sys, nameof (nested_sys. hej. plant_input))
125+ ]
126+ open_sys, (du, u) = open_loop (sys, ap)
127+ matrices, _ = linearize (open_sys, [du], [u])
128+ @test matrices. A[] == - 1
129+ @test matrices. B[] * matrices. C[] == - 1 # either one negative
130+ @test matrices. D[] == 0
131+ end
132+
133+ # Multiple analysis points
134+
135+ eqs = [connect (P. output, :plant_output , C. input)
136+ connect (C. output, :plant_input , P. input)]
137+ sys = ODESystem (eqs, t, systems = [P, C], name = :hej )
138+ @named nested_sys = ODESystem (Equation[], t; systems = [sys])
139+
140+ @testset " get_sensitivity - $name " for (name, sys, ap) in [
141+ (" inner" , sys, sys. plant_input),
142+ (" nested" , nested_sys, nested_sys. hej. plant_input),
143+ (" inner - nonamespace" , sys, :plant_input ),
144+ (" inner - Symbol" , sys, nameof (sys. plant_input)),
145+ (" nested - Symbol" , nested_sys, nameof (nested_sys. hej. plant_input))
146+ ]
147+ matrices, _ = get_sensitivity (sys, ap)
148+ @test matrices. A[] == - 2
149+ @test matrices. B[] * matrices. C[] == - 1 # either one negative
150+ @test matrices. D[] == 1
151+ end
152+
153+ @testset " linearize - $name " for (name, sys, inputap, outputap) in [
154+ (" inner" , sys, sys. plant_input, sys. plant_output),
155+ (" nested" , nested_sys, nested_sys. hej. plant_input, nested_sys. hej. plant_output)
156+ ]
157+ @testset " input - $(typeof (input)) , output - $(typeof (output)) " for (input, output) in [
158+ (inputap, outputap),
159+ (nameof (inputap), outputap),
160+ (inputap, nameof (outputap)),
161+ (nameof (inputap), nameof (outputap)),
162+ (inputap, [outputap]),
163+ (nameof (inputap), [outputap]),
164+ (inputap, [nameof (outputap)]),
165+ (nameof (inputap), [nameof (outputap)])
166+ ]
167+ if input isa Symbol
168+ # broken because MTKStdlib defines the method for
169+ # `input::Union{Symbol, Vector{Symbol}}` which we can't directly call
170+ @test_broken linearize (sys, input, output)
171+ linfun, ssys = @invoke linearization_function (sys:: AbstractSystem ,
172+ input:: Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}} ,
173+ output:: Any )
174+ matrices = linearize (ssys, linfun)
175+ else
176+ matrices, _ = linearize (sys, input, output)
177+ end
178+ # Result should be the same as feedpack(P, 1), i.e., the closed-loop transfer function from plant input to plant output
179+ @test matrices. A[] == - 2
180+ @test matrices. B[] * matrices. C[] == 1 # both positive
181+ @test matrices. D[] == 0
182+ end
183+ end
184+
185+ @testset " linearize - variable output - $name " for (name, sys, input, output) in [
186+ (" inner" , sys, sys. plant_input, P. output. u),
187+ (" nested" , nested_sys, nested_sys. hej. plant_input, sys. P. output. u)
188+ ]
189+ matrices, _ = linearize (sys, input, [output])
190+ @test matrices. A[] == - 2
191+ @test matrices. B[] * matrices. C[] == 1 # both positive
192+ @test matrices. D[] == 0
193+ end
194+
195+ using ModelingToolkit, OrdinaryDiffEq, LinearAlgebra
196+ using ModelingToolkitStandardLibrary. Mechanical. Rotational
197+ using ModelingToolkitStandardLibrary. Blocks: Sine, PID, SecondOrder, Step, RealOutput
198+ using ModelingToolkit: connect
199+
200+ @testset " Complicated model" begin
201+ # Parameters
202+ m1 = 1
203+ m2 = 1
204+ k = 1000 # Spring stiffness
205+ c = 10 # Damping coefficient
206+ @named inertia1 = Inertia (; J = m1)
207+ @named inertia2 = Inertia (; J = m2)
208+ @named spring = Spring (; c = k)
209+ @named damper = Damper (; d = c)
210+ @named torque = Torque ()
211+
212+ function SystemModel (u = nothing ; name = :model )
213+ eqs = [connect (torque. flange, inertia1. flange_a)
214+ connect (inertia1. flange_b, spring. flange_a, damper. flange_a)
215+ connect (inertia2. flange_a, spring. flange_b, damper. flange_b)]
216+ if u != = nothing
217+ push! (eqs, connect (torque. tau, u. output))
218+ return ODESystem (eqs, t;
219+ systems = [
220+ torque,
221+ inertia1,
222+ inertia2,
223+ spring,
224+ damper,
225+ u
226+ ],
227+ name)
228+ end
229+ ODESystem (eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name)
230+ end
231+
232+ @named r = Step (start_time = 0 )
233+ model = SystemModel ()
234+ @named pid = PID (k = 100 , Ti = 0.5 , Td = 1 )
235+ @named filt = SecondOrder (d = 0.9 , w = 10 )
236+ @named sensor = AngleSensor ()
237+ @named er = Add (k2 = - 1 )
238+
239+ connections = [connect (r. output, :r , filt. input)
240+ connect (filt. output, er. input1)
241+ connect (pid. ctr_output, :u , model. torque. tau)
242+ connect (model. inertia2. flange_b, sensor. flange)
243+ connect (sensor. phi, :y , er. input2)
244+ connect (er. output, :e , pid. err_input)]
245+
246+ closed_loop = ODESystem (connections, t, systems = [model, pid, filt, sensor, r, er],
247+ name = :closed_loop , defaults = [
248+ model. inertia1. phi => 0.0 ,
249+ model. inertia2. phi => 0.0 ,
250+ model. inertia1. w => 0.0 ,
251+ model. inertia2. w => 0.0 ,
252+ filt. x => 0.0 ,
253+ filt. xd => 0.0
254+ ])
255+
256+ sys = structural_simplify (closed_loop)
257+ prob = ODEProblem (sys, unknowns (sys) .=> 0.0 , (0.0 , 4.0 ))
258+ sol = solve (prob, Rodas5P (), reltol = 1e-6 , abstol = 1e-9 )
259+ end
0 commit comments