Skip to content

Commit 929b9f5

Browse files
committed
finally working
1 parent af8a3dd commit 929b9f5

File tree

2 files changed

+47
-31
lines changed

2 files changed

+47
-31
lines changed

docs/src/batch_linearization.md

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,18 @@ closed_loop_eqs = [
115115
plot(layout=2)
116116
117117
# Simulate each individual controller
118-
for C in Cs
119-
@named Ci = System(C)
120-
eqs = [
121-
closed_loop_eqs
122-
connect(fb.output, Ci.input)
123-
connect(Ci.output, duffing.u)
124-
]
125-
@named closed_loop = System(eqs, t, systems=[duffing, Ci, fb, ref, F])
126-
prob = ODEProblem(structural_simplify(closed_loop), [F.x => 0, F.xd => 0], (0.0, 8.0))
127-
sol = solve(prob, Rodas5P(), abstol=1e-8, reltol=1e-8)
128-
plot!(sol, idxs=[duffing.y.u, duffing.u.u], layout=2, lab="")
129-
end
118+
# for C in Cs
119+
# @named Ci = System(C)
120+
# eqs = [
121+
# closed_loop_eqs
122+
# connect(fb.output, Ci.input)
123+
# connect(Ci.output, duffing.u)
124+
# ]
125+
# @named closed_loop = System(eqs, t, systems=[duffing, Ci, fb, ref, F])
126+
# prob = ODEProblem(structural_simplify(closed_loop), [F.x => 0, F.xd => 0], (0.0, 8.0))
127+
# sol = solve(prob, Rodas5P(), abstol=1e-8, reltol=1e-8)
128+
# plot!(sol, idxs=[duffing.y.u, duffing.u.u], layout=2, lab="")
129+
# end
130130
131131
# Simulate gain-scheduled controller
132132
@named Cgs = GainScheduledStateSpace(Cs, xs, interpolator=LinearInterpolation)
@@ -162,11 +162,12 @@ timepoints = 0:0.01:8
162162
Ps2, ssys, ops2, resolved_ops = trajectory_ss(closed_loop, closed_loop.r, closed_loop.y, sol; t=timepoints, verbose=true);
163163
bodeplot(Ps2, w, legend=false)
164164
```
165+
Not how the closed-loop system changes very little along the trajectory, this is a good indication that the gain-scheduled controller is able to make the system appear linear.
165166

166167
Internally, [`trajectory_ss`](@ref) works very much the same as [`batch_ss`](@ref), but constructs operating points automatically along the trajectory. This requires that the solution contains the states of the simplified system, accessible through the `idxs` argument like `sol(t, idxs=x)`. By linearizing the same system as we simulated, we ensure that this condition holds, doing so requires that we specify the inputs and outputs as analysis points rather than as variables.
167168

168169

169-
Notice how the peak of the transfer function changes along the trajectory. We can replicate the figure above by linearizing the plant and the controller individually, by providing the `loop_openings` argument. When linearizing the plant, we disconnect the controller input by passing `loop_openings=[closed_loop.u]`, and when linearizing the controller, we have various options for disconnecting the the plant:
170+
We can replicate the figure above by linearizing the plant and the controller individually, by providing the `loop_openings` argument. When linearizing the plant, we disconnect the controller input by passing `loop_openings=[closed_loop.u]`, and when linearizing the controller, we have various options for disconnecting the the plant:
170171
- Break the connection from plant output to controller input by passing `loop_openings=[closed_loop.y]`
171172
- Break the connection between the controller and the plant input by passing `loop_openings=[closed_loop.u]`
172173
- Break the connection `y` as well as the scheduling variable `v` (which is another form of feedback) by passing `loop_openings=[closed_loop.y, closed_loop.v]`
@@ -207,7 +208,7 @@ bodeplot(closed_loops_mimo, w; title="Loop open at MIMO", kwargs...)
207208

208209

209210

210-
Why the discrepancy? We can understand the difference by comparing the Bode plots of the controllers.
211+
Why are the results different depending on where we open the loop? We can understand the difference by comparing the Bode plots of the controllers.
211212

212213
```@example BATCHLIN
213214
plot(
@@ -217,9 +218,23 @@ plot(
217218
bodeplot(controllersv, w, legend=false, plotphase=false, title="Loop open at v and y"),
218219
)
219220
```
220-
if we open at both `y` and `v`, we only get the controller designed for the origin. If we open at `u`, we get controllers for the different values of the scheduling variable, and the corresponding measurement feedback (which is the same as the scheduling variable in this case).
221+
if we open at both `y` and `v` or we open at `u`, we get controllers for the different values of the scheduling variable, and the corresponding measurement feedback (which is the same as the scheduling variable in this case).
222+
223+
However, if we only open at `y` we get controller linearizations that _still contain the closed loop through the scheduling connection_ `v`. We can verify this by looking at what variables are present in the input-output map
224+
```@example BATCHLIN
225+
Cy = named_ss(controllersy[end], x=Symbol.(unknowns(ssy)))
226+
sminreal(Cy).x
227+
```
228+
notice how the state of the plant is included in the controller, this is an indication that we didn't fully isolate the controller during the linearizaiton. If we do the same thing for the controller with the loop opened at `u`, we see that the state of the plant is not included in the controller:
229+
```@example BATCHLIN
230+
Cu = named_ss(controllersu[end], x=Symbol.(unknowns(ssu)))
231+
sminreal(Cu).x
232+
```
233+
The call to `sminreal` is important here, it removes the states that are not needed to represent the input-output map of the system. The state of the full model, including the plant state, is present in the linearization before this call.
234+
235+
221236

222-
However, if we only open at `y` we get controller linearizations that contains the closed loop through the scheduling connection `v`. The easiest way to ensure that the controller is properly disconnected from the plant while taking into account the different scheduling along the trajectory is thus to break at `u`.
237+
The easiest way to ensure that the controller is properly disconnected from the plant while taking into account the different scheduling along the trajectory is thus to break at `u`.
223238

224239
## Summary
225240
We have seen how to

src/ode_system.jl

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -405,16 +405,13 @@ function batch_ss(args...; kwargs...)
405405
[ss(l...) for l in lins], ssys, resolved_ops
406406
end
407407

408-
function unnamespace(ap)
409-
# ap_name = ap.name
410-
# new_name = join(ModelingToolkit.namespace_hierarchy(ap_name)[2:end], Symbolics.NAMESPACE_SEPARATOR)
411-
# ap2 = ModelingToolkit.@set ap.name = Symbol(new_name)
412-
# ap2
413-
414-
ap_name = ModelingToolkit.SymbolicIndexingInterface.getname(ap.input.u)
415-
new_name = join(ModelingToolkit.namespace_hierarchy(ap_name)[2:end], Symbolics.NAMESPACE_SEPARATOR)
416-
new_var = Symbolics.rename(ap.input.u, Symbol(new_name))
417-
end
408+
# function unnamespace(ap)
409+
# map(ap.outputs) do out
410+
# ap_name = ModelingToolkit.SymbolicIndexingInterface.getname(out.u)
411+
# new_name = join(ModelingToolkit.namespace_hierarchy(ap_name)[2:end], Symbolics.NAMESPACE_SEPARATOR)
412+
# Symbolics.rename(ap.input.u, Symbol(new_name))
413+
# end
414+
# end
418415

419416
"""
420417
linsystems, ssys = trajectory_ss(sys, inputs, outputs, sol; t = _max_100(sol.t), fuzzer=nothing, verbose = true, kwargs...)
@@ -440,11 +437,11 @@ function trajectory_ss(sys, inputs, outputs, sol; t = _max_100(sol.t), allow_inp
440437

441438
# TODO: The value of the output (or input) of the input analysis points should be mapped to the perturbation vars
442439
perturbation_vars = ModelingToolkit.inputs(ssys)
443-
original_inputs = [unnamespace(ap) for ap in vcat(inputs)] # assuming all inputs are analysis points for now
440+
# @show original_inputs = reduce(vcat, unnamespace(ap) for ap in vcat(inputs)) # assuming all inputs are analysis points for now
444441
op_nothing = Dict(unknowns(sys) .=> nothing) # Remove all defaults present in the original system
445442
defs = ModelingToolkit.defaults(sys)
446443
ops = map(t) do ti
447-
opsol = Dict(x => robust_sol_getindex(sol, ti, x, defs; verbose) for x in [x; original_inputs])
444+
opsol = Dict(x => robust_sol_getindex(sol, ti, x, defs; verbose) for x in x)
448445
# opsolu = Dict(new_u => robust_sol_getindex(sol, ti, u, defs; verbose) for (new_u, u) in zip(perturbation_vars, original_inputs))
449446
merge(op_nothing, opsol)
450447
end
@@ -455,11 +452,15 @@ function trajectory_ss(sys, inputs, outputs, sol; t = _max_100(sol.t), allow_inp
455452
ops = reduce(vcat, opsv)
456453
t = repeat(t, inner = length(ops) ÷ length(t))
457454
end
458-
lin_fun, ssys = linearization_function(sys, inputs, outputs; op=ops[1], initialization_abstol=1e-2, initialization_reltol=1e-2, kwargs...) # initializealg=ModelingToolkit.SciMLBase.NoInit()
455+
lin_fun, ssys = linearization_function(sys, inputs, outputs; op=ops[1], kwargs...)#, initialization_abstol=1e-1, initialization_reltol=1e-1, kwargs...) # initializealg=ModelingToolkit.SciMLBase.NoInit()
456+
# Main.lin_fun = lin_fun
457+
# Main.op1 = ops[1]
458+
# Main.ops = ops
459+
# equations(lin_fun.prob.f.initialization_data.initializeprob.f.sys)
460+
# observed(lin_fun.prob.f.initialization_data.initializeprob.f.sys)
459461
lins_ops = map(zip(ops, t)) do (op, t)
460-
@show t
461462
linearize(ssys, lin_fun; op, t, allow_input_derivatives)
462-
# linearize(sys, inputs, outputs; op, t, allow_input_derivatives, initialize=false)[1]
463+
# linearize(sys, inputs, outputs; op, t, allow_input_derivatives) # useful for debugging
463464
end
464465
lins = first.(lins_ops)
465466
resolved_ops = last.(lins_ops)

0 commit comments

Comments
 (0)