1+ include (" planar_flat.jl" ) # Get FlatDyadBot
2+
3+ using DyadControlSystems
4+ import DyadControlSystems as JSC
5+ using LinearAlgebra
6+
7+ # Create the plant directly - no wrapper component needed
8+
9+ @component function Add4 (; name, K)
10+ __vars = Any[]
11+ __systems = System[]
12+ __guesses = Dict ()
13+ __defaults = Dict ()
14+ __initialization_eqs = []
15+ __eqs = Equation[]
16+
17+ r,c = size (K)
18+ # ## Symbolic Parameters
19+ __params = @parameters begin
20+ K[1 : r, 1 : c] = K
21+ end
22+ append! (__vars, @variables (input1 (t):: Real ), [input = true ])
23+ append! (__vars, @variables (input2 (t):: Real ), [input = true ])
24+ append! (__vars, @variables (input3 (t):: Real ), [input = true ])
25+ append! (__vars, @variables (input4 (t):: Real ), [input = true ])
26+ append! (__vars, @variables (output (t):: Real ), [output = true ])
27+ __constants = Any[]
28+ push! (__eqs, output ~ dot (K, [input1, input2, input3, input4]))
29+ return System (__eqs, t, __vars, __params; systems= __systems, defaults= __defaults, guesses= __guesses, name, initialization_eqs= __initialization_eqs)
30+ end
31+
32+ @component function LQGFlatDyadBot2 (; name)
33+ systems = @named begin
34+ plant = FlatDyadBot ()
35+ L = Add4 (K = ones (1 , 4 ))
36+ end
37+
38+ eqs = [
39+ # Expose outputs with analysis point names for LQG
40+ connect (plant. x_output. u, :y_x , L. input1) # Position measurement
41+ connect (plant. x_dot_output. u, :y_xd , L. input2)
42+ connect (plant. theta_output. u, :y_theta , L. input3) # Angle measurement
43+ connect (plant. theta_dot_output. u, :y_thetad , L. input4)
44+ connect (L. output, :u , plant. control_input. u) # Control input
45+ ]
46+
47+ System (eqs, t, [], []; systems, name)
48+ end
49+
50+
51+
52+ @named plant = LQGFlatDyadBot2 ()
53+
54+ # LQG Analysis Specification
55+ # - 2 measurements: position (x) and angle (theta)
56+ # - 2 controlled outputs: same as measurements (regulate both)
57+ # - 1 control input: torque (tau)
58+ lqg_spec = JSC. LQGAnalysisSpec (;
59+ name = :SegwayLQG ,
60+ model = plant,
61+ measurement = [" y_x" , " y_theta" ], # What we measure
62+ controlled_output = [" y_x" , " y_xd" , " y_theta" , " y_thetad" ], # What we want to control
63+ control_input = [" u" ], # Control input
64+ q1_diag = [10.0 , 0.1 , 1 , 0.1 ], # Penalty on controlled outputs (x, theta)
65+ q2_diag = [0.0001 ], # Penalty on control input (tau)
66+ r1_diag = [1.0 ], # Disturbance noise covariance
67+ r2_diag = [0.001 , 0.001 ], # Measurement noise covariance (x, theta)
68+ wl = 1e-2 ,
69+ wu = 1e3 ,
70+ num_frequencies = 200 ,
71+ )
72+
73+ # Run the LQG analysis
74+ lqg_asol = JSC. run_analysis (lqg_spec)
75+
76+ # Visualize results
77+ step_response = JSC. artifacts (lqg_asol, :StepResponse )
78+ gang_of_four = JSC. artifacts (lqg_asol, :GangOfFour )
79+ bode_plot = JSC. artifacts (lqg_asol, :BodePlot )
80+ margin_plot = JSC. artifacts (lqg_asol, :MarginPlot )
81+ controller_gain = lqg_asol. L
82+ observer_gain = lqg_asol. K
83+
84+ display (controller_gain)
85+ display (observer_gain)
86+
87+ # #
88+
89+ # cm = complete(plant)
90+ ssys = mtkcompile (plant)
91+ op = [
92+ ssys. L. K => - lqg_asol. L / lqg_asol. P_reduced. C
93+ ]
94+
95+
96+ prob = ODEProblem (ssys, op, (0 , 1 ))
97+ sol = solve (prob, Rodas5P ())
98+ plot (sol, ylims= (- 5 , 5 )); hline! ([π], l= (:dash , :black ), primary= false )
99+
100+
101+ # #
102+
103+ get_Cfffb (; name) = System (ss (lqg_asol. Cfffb); name)
104+ get_Cff (; name) = System (ss (lqg_asol. Cff); name)
105+ get_Cfb (; name) = System (ss (lqg_asol. Cfb); name)
106+
107+ @component function LQGFlatDyadBot4 (; name)
108+ systems = @named begin
109+ plantfffb = FlatDyadBot ()
110+ C = get_Cfffb ()
111+ plant = FlatDyadBot ()
112+ Cff = get_Cff ()
113+ Cfb = get_Cfb ()
114+ add = Blocks. Add (k1= 1 , k2= - 1 )
115+ end
116+
117+ eqs = [
118+ C. input. u[1 ] ~ ifelse (t> 5 , 0.15 , 0 ) # rx
119+ C. input. u[2 ] ~ 0 # rxd
120+ C. input. u[3 ] ~ 0 # rtheta
121+ C. input. u[4 ] ~ 0 # rthetad
122+ C. input. u[5 ] ~ plantfffb. x_output. u # x
123+ C. input. u[6 ] ~ plantfffb. theta_output. u- pi # theta
124+ connect (C. output, :ufffb , plantfffb. control_input)
125+
126+ Cff. input. u[1 ] ~ ifelse (t> 5 , 0.15 , 0 ) # rx
127+ Cff. input. u[2 ] ~ 0 # rxd
128+ Cff. input. u[3 ] ~ 0 # rtheta
129+ Cff. input. u[4 ] ~ 0 # rthetad
130+ Cfb. input. u[1 ] ~ plant. x_output. u # x
131+ Cfb. input. u[2 ] ~ plant. theta_output. u- pi # theta
132+ connect (Cff. output, add. input1)
133+ connect (Cfb. output, add. input2)
134+ connect (add. output, :u , plant. control_input)
135+ ]
136+ guesses = [
137+ plantfffb. theta_ddot => 0 ,
138+ plant. theta_ddot => 0
139+ ]
140+ System (eqs, t, [], []; systems, name, guesses)
141+ end
142+
143+ @named lqg_cl = LQGFlatDyadBot4 ()
144+
145+ ssys = mtkcompile (lqg_cl)
146+
147+ op = [
148+ # collect(ssys.C.x) .=> nothing
149+ # ssys.C.output.u => 0
150+ # D(ssys.C.output.u) => 0
151+ ssys. plant. theta => deg2rad (160 )
152+ ssys. plantfffb. theta => deg2rad (160 )
153+ ]
154+
155+ prob = ODEProblem (ssys, op, (0 , 20 ))
156+ sol = solve (prob, Rodas5P ())
157+ plot (sol, idxs= [
158+ ssys. plant. theta
159+ ssys. plant. x
160+ ssys. plant. tau
161+ ssys. plantfffb. theta
162+ ssys. plantfffb. x
163+ ssys. plantfffb. tau
164+ ]); hline! ([π 0.3 ], l= (:dash , :black ), primary= false , ylims= (- 1 , 4 ))
165+
166+
167+ # #
168+
169+ LT = lqg_asol. Cfb* system_mapping (lqg_asol. P_ext)
170+
171+ plot (diskmargin (LT))
172+
173+ # #
174+ L2 = get_named_looptransfer (lqg_cl, [lqg_cl. u])
175+ L2 = - L2# minreal(L2, 1e-12)
176+
177+ bodeplot ([LT, L2]) # why not identical?
178+
179+
180+ dmi2 = diskmargin (L2)
181+ plot! (dmi2)
182+
183+ # TODO : test the Cff and Cfb separate contorllers, they do not appear to agree with Cfffb at all
0 commit comments