├── .gitignore ├── LICENSE ├── README.md ├── data ├── USA_AZ_Phoenix.722780_TMY2.mos ├── building_data.csv └── fakedataset └── solutions ├── problem0.jl ├── problem1.jl ├── problem2.jl ├── problem3.jl ├── problem4.jl ├── problem5.html ├── problem5.jl ├── problem6.jl └── rc.jl /.gitignore: -------------------------------------------------------------------------------- 1 | # Files generated by invoking Julia with --code-coverage 2 | *.jl.cov 3 | *.jl.*.cov 4 | 5 | # Files generated by invoking Julia with --track-allocation 6 | *.jl.mem 7 | 8 | # System-specific files and directories generated by the BinaryProvider and BinDeps packages 9 | # They contain absolute paths specific to the host computer, and so should not be committed 10 | deps/deps.jl 11 | deps/build.log 12 | deps/downloads/ 13 | deps/usr/ 14 | deps/src/ 15 | 16 | # Build artifacts for creating documentation generated by the Documenter package 17 | docs/build/ 18 | docs/site/ 19 | 20 | # File generated by Pkg, the package manager, based on a corresponding Project.toml 21 | # It records a fixed state of all packages used by the project. As such, it should not be 22 | # committed for packages, but should be committed for applications that require a static 23 | # environment. 24 | Manifest.toml 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Julia Computing, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModelingToolkitWorkshop 2 | 3 | Training materials for ModelingToolkit and JuliaSim 4 | 5 | ## Exercises 6 | 7 | The exercise sheet can be found [at this Overleaf link](https://www.overleaf.com/read/mcvtqwjcmkrj). 8 | -------------------------------------------------------------------------------- /data/fakedataset: -------------------------------------------------------------------------------- 1 | a blank dataset 2 | -------------------------------------------------------------------------------- /solutions/problem0.jl: -------------------------------------------------------------------------------- 1 | # https://github.com/JuliaComputing/ModelingToolkitWorkshop 2 | using ModelingToolkit, Plots, DifferentialEquations 3 | 4 | @variables t x(t) y(t) 5 | @parameters α β δ γ 6 | D = Differential(t) 7 | eqs = [ 8 | D(x) ~ α*x - β*x*y 9 | D(y) ~ δ*x*y - γ*y 10 | ]; 11 | 12 | @named model = ODESystem(eqs, t); 13 | 14 | prob = ODEProblem(model, [x => 0.9, y=>1.8], (0, 20.0), 15 | [α => 2/3, β => 4/3, γ => 1, δ => 1]) 16 | sol = solve(prob) 17 | plot(sol) 18 | -------------------------------------------------------------------------------- /solutions/problem1.jl: -------------------------------------------------------------------------------- 1 | using ModelingToolkit, DifferentialEquations, Plots 2 | @variables t 3 | @variables begin 4 | y1(t) = 1.0 5 | y2(t) = 0.0 6 | y3(t) = 0.0 7 | y4(t) = 0.0 8 | y5(t) = 0.0 9 | y6(t) = 0.0 10 | y7(t) = 0.0 11 | y8(t) = 0.0057 12 | end 13 | @parameters begin 14 | k1 = 1.71 15 | k2 = 280.0 16 | k3 = 8.32 17 | k4 = 0.69 18 | k5 = 0.43 19 | k6 = 1.81 20 | end 21 | D = Differential(t) 22 | eqs = [ 23 | D(y1) ~ (-k1*y1 + k5*y2 + k3*y3 + 0.0007), 24 | D(y2) ~ (k1*y1 - 8.75*y2), 25 | D(y3) ~ (-10.03*y3 + k5*y4 + 0.035*y5), 26 | D(y4) ~ (k3*y2 + k1*y3 - 1.12*y4), 27 | D(y5) ~ (-1.745*y5 + k5*y6 + k5*y7), 28 | D(y6) ~ (-k2*y6*y8 + k4*y4 + k1*y5 - k5*y6 + k4*y7), 29 | D(y7) ~ (k2*y6*y8 - k6*y7), 30 | D(y8) ~ (-k2*y6*y8 + k6*y7) 31 | ] 32 | @named hires_model = ODESystem(eqs) 33 | 34 | prob = ODEProblem(hires_model, [], (0.0, 321.8122)) 35 | sol = solve(prob) 36 | pp = plot(sol, dpi=400) 37 | -------------------------------------------------------------------------------- /solutions/problem2.jl: -------------------------------------------------------------------------------- 1 | module RLC 2 | 3 | using ModelingToolkit 4 | 5 | @variables t 6 | @connector function Pin(;name) 7 | sts = @variables v(t)=1.0 i(t)=1.0 [connect = Flow] 8 | ODESystem(Equation[], t, sts, []; name=name) 9 | end 10 | 11 | function Ground(;name) 12 | @named g = Pin() 13 | eqs = [g.v ~ 0] 14 | compose(ODESystem(eqs, t, [], []; name=name), g) 15 | end 16 | 17 | function OnePort(;name) 18 | @named p = Pin() 19 | @named n = Pin() 20 | sts = @variables v(t)=1.0 i(t)=1.0 21 | eqs = [ 22 | v ~ p.v - n.v 23 | 0 ~ p.i + n.i 24 | i ~ p.i 25 | ] 26 | compose(ODESystem(eqs, t, sts, []; name=name), p, n) 27 | end 28 | 29 | function Resistor(;name, R = 1.0) 30 | @named oneport = OnePort() 31 | @unpack v, i = oneport 32 | ps = @parameters R=R 33 | eqs = [ 34 | v ~ i * R 35 | ] 36 | extend(ODESystem(eqs, t, [], ps; name=name), oneport) 37 | end 38 | 39 | function Capacitor(;name, C = 1.0) 40 | @named oneport = OnePort() 41 | @unpack v, i = oneport 42 | ps = @parameters C=C 43 | D = Differential(t) 44 | eqs = [ 45 | D(v) ~ i / C 46 | ] 47 | extend(ODESystem(eqs, t, [], ps; name=name), oneport) 48 | end 49 | 50 | function Inductor(; name, L = 1.0) 51 | @named oneport = OnePort() 52 | @unpack v, i = oneport 53 | ps = @parameters L = L 54 | D = Differential(t) 55 | eqs = [ 56 | D(i) ~ v / L, 57 | ] 58 | extend(ODESystem(eqs, t, [], ps; name = name), oneport) 59 | end 60 | 61 | function ConstantVoltage(;name, V = 1.0) 62 | @named oneport = OnePort() 63 | @unpack v = oneport 64 | ps = @parameters V=V 65 | eqs = [ 66 | V ~ v 67 | ] 68 | extend(ODESystem(eqs, t, [], ps; name=name), oneport) 69 | end 70 | 71 | end 72 | 73 | using .RLC 74 | using ModelingToolkit, Plots, DifferentialEquations 75 | L = C = V = R = 1.0 76 | @named inductor = RLC.Inductor(L=L) 77 | @named resistor = RLC.Resistor(R=R) 78 | @named capacitor = RLC.Capacitor(C=C) 79 | @named source = RLC.ConstantVoltage(V=V) 80 | @named ground = RLC.Ground() 81 | 82 | rlc_eqs = [ 83 | connect(source.p, resistor.p) 84 | connect(resistor.n, inductor.p) 85 | connect(inductor.n, capacitor.p) 86 | connect(capacitor.n, source.n) 87 | connect(capacitor.n, ground.g) 88 | ] 89 | 90 | @named _rlc_model = ODESystem(rlc_eqs, t) 91 | @named rlc_model = compose(_rlc_model, 92 | [inductor, resistor, capacitor, source, ground]) 93 | sys = structural_simplify(rlc_model) 94 | u0 = [ 95 | capacitor.v => 0.0 96 | ] 97 | prob = ODEProblem(sys, u0, (0, 10.0)) 98 | sol = solve(prob) 99 | plot(sol) 100 | -------------------------------------------------------------------------------- /solutions/problem3.jl: -------------------------------------------------------------------------------- 1 | using ModelingToolkit, DifferentialEquations, Plots, ControlSystemsBase 2 | using ModelingToolkitStandardLibrary, ModelingToolkitStandardLibrary.Electrical, ModelingToolkitStandardLibrary.Mechanical.Rotational, ModelingToolkitStandardLibrary.Blocks 3 | @variables t 4 | # R = 0.5 # [Ohm] armature resistance 5 | # L = 4.5e-3 # [H] armature inductance 6 | # k = 0.5 # [N.m/A] motor constant 7 | # J = 0.02 # [kg.m²] inertia 8 | # f = 0.01 # [N.m.s/rad] friction factor 9 | function Motor(; name, R = 0.5, L = 4.5e-3, k = 0.5, J = 0.02, f = 0.01) 10 | @named ground = Ground() 11 | @named source = Voltage() 12 | @named R1 = Resistor(R = R) 13 | @named L1 = Inductor(L = L) 14 | @named emf = EMF(k = k) 15 | @named fixed = Fixed() 16 | @named load = Torque(use_support = false) 17 | @named inertia = Inertia(J = J) 18 | @named friction = Damper(d = f) 19 | 20 | connections = [connect(fixed.flange, emf.support, friction.flange_b) 21 | connect(emf.flange, friction.flange_a, inertia.flange_a) 22 | connect(inertia.flange_b, load.flange) 23 | connect(source.p, R1.p) 24 | connect(R1.n, L1.p) 25 | connect(L1.n, emf.p) 26 | connect(emf.n, source.n, ground.g)] 27 | subcomps = [ 28 | ground, 29 | source, 30 | R1, 31 | L1, 32 | emf, 33 | fixed, 34 | load, 35 | inertia, 36 | friction, 37 | ] 38 | @named model = ODESystem(connections, t) 39 | compose(model, subcomps) 40 | end 41 | 42 | pi_k = 1.1 43 | pi_T = 0.05 44 | @named motor = Motor(); 45 | tau_L_step = -0.3 # [N.m] amplitude of the load torque step 46 | @named ref = Blocks.Step(height = 1, start_time = 0) 47 | @named pi_controller = Blocks.LimPI(k = pi_k, T = pi_T, u_max = 10, Ta = 0.035) 48 | @named feedback = Blocks.Feedback() 49 | @named load_step = Blocks.Step(height = tau_L_step, start_time = 3) 50 | @named speed_sensor = SpeedSensor() 51 | 52 | connections = [ 53 | connect(motor.load.flange, speed_sensor.flange) 54 | connect(ref.output, feedback.input1) 55 | connect(speed_sensor.w, :y, feedback.input2) 56 | connect(load_step.output, motor.load.tau) 57 | connect(feedback.output, pi_controller.err_input) 58 | connect(pi_controller.ctr_output, :u, motor.source.V)] 59 | 60 | subcomps = [ 61 | motor, 62 | ref, 63 | pi_controller, 64 | feedback, 65 | load_step, 66 | speed_sensor, 67 | ] 68 | @named model = ODESystem(connections, t) 69 | model = compose(model, subcomps) 70 | 71 | sys = structural_simplify(model) 72 | prob = ODEProblem(sys, [], (0, 6.0)) 73 | sol = solve(prob, Rodas4()) 74 | 75 | p1 = Plots.plot(sol.t, sol[motor.inertia.w], ylabel = "Angular Vel. in rad/s", 76 | label = "Measurement", title = "DC Motor with Speed Controller") 77 | Plots.plot!(sol.t, sol[ref.output.u], label = "Reference") 78 | p2 = Plots.plot(sol.t, sol[motor.load.tau.u], ylabel = "Disturbance in Nm", label = "") 79 | Plots.plot(p1, p2, layout = (2, 1)) 80 | 81 | mat, simplified_sys = get_sensitivity(model, :y); 82 | S = ss(mat...); 83 | bplot = bodeplot(S, plotphase=false) 84 | nplot = nyquistplot(-ss(get_looptransfer(model, :u)[1]...)) 85 | Plots.plot(p1, p2, bplot, nplot, layout = (2, 2)) 86 | -------------------------------------------------------------------------------- /solutions/problem4.jl: -------------------------------------------------------------------------------- 1 | using BuildingModelLibrary 2 | using JuliaSimModelOptimizer 3 | using ModelingToolkit 4 | using OrdinaryDiffEq 5 | using Unitful 6 | using DataFrames, CSV 7 | using OptimizationOptimJL 8 | using DataInterpolations 9 | 10 | datapath = joinpath(@__DIR__, "..", "data", "USA_AZ_Phoenix.722780_TMY2.mos") 11 | model = initialize_model(;datapath) 12 | 13 | df = BuildingModelLibrary.getdata(datapath) 14 | idx_range = 4560:4728+1 15 | ts = df[idx_range, :t] .- df[idx_range[1], :t] 16 | tspan = (0.0, ts[end-1]) 17 | 18 | data = CSV.read(joinpath(@__DIR__, "..", "data", "building_data.csv"), DataFrame) 19 | 20 | trial = Trial(data, model; tspan, alg=QNDF(autodiff=false)) 21 | 22 | invprob = InverseProblem([trial], model, 23 | [ 24 | @nonamespace(model.T_fluids_1₊k) => (270.0, 280.0), 25 | @nonamespace(model.T_fluids_2₊k) => (270.0, 280.0), 26 | @nonamespace(model.T_fluids_3₊k) => (270.0, 280.0) 27 | ] 28 | ) 29 | 30 | # alg = SplineCollocate(maxiters=1000, solver=BFGS(), interp=CubicSpline) 31 | alg = StochGlobalOpt(maxiters=100) 32 | result = calibrate(invprob, alg) 33 | uconvert.(u"°C", result .* u"K") 34 | -------------------------------------------------------------------------------- /solutions/problem5.jl: -------------------------------------------------------------------------------- 1 | using JuliaSimControl, ModelingToolkit, OrdinaryDiffEq, JuliaSimControl.MPC, LowLevelParticleFilters, LinearAlgebra, Plots 2 | using LinearAlgebra:cross, inv 3 | 4 | rcam_constants = Dict{Symbol, Float64}(:m => 120000, 5 | :c̄ => 6.6, 6 | :lt => 24.8, 7 | :S => 260, 8 | :St => 64, 9 | :Xcg => 0.23 * 6.6, 10 | :Ycg => 0.0, 11 | :Zcg => 0.10 * 6.6, 12 | :Xac => 0.12 * 6.6, 13 | :Yac => 0.0, 14 | :Zac => 0.0, 15 | :Xapt1 => 0, 16 | :Yapt1 => -7.94, 17 | :Zapt1 => -1.9, 18 | :Xapt2 => 0, 19 | :Yapt2 => 7.94, 20 | :Zapt2 => -1.9, 21 | :g => 9.81, 22 | :depsda => 0.25, 23 | :α_L0 => deg2rad(-11.5), 24 | :n => 5.5, 25 | :a3 => -768.5, 26 | :a2 => 609.2, 27 | :a1 => -155.2, 28 | :a0 => 15.212, 29 | :α_switch => deg2rad(14.5), 30 | :ρ => 1.225,) 31 | 32 | # Inertia matrices 33 | 34 | Ib_mat = [40.07 0.0 -2.0923 35 | 0.0 64.0 0.0 36 | -2.0923 0.0 99.92]*rcam_constants[:m] 37 | 38 | Ib = Ib_mat 39 | 40 | invIb = inv(Ib_mat) 41 | 42 | ps = @parameters( 43 | ρ = rcam_constants[:ρ], [description="kg/m3 - air density"], 44 | m = rcam_constants[:m], [description="kg - total mass"], 45 | c̄ = rcam_constants[:c̄], [description="m - mean aerodynamic chord"], 46 | lt = rcam_constants[:lt], [description="m - tail AC distance to CG"], 47 | S = rcam_constants[:S], [description="m2 - wing area"], 48 | St = rcam_constants[:St], [description="m2 - tail area"], 49 | Xcg = rcam_constants[:Xcg], [description="m - x pos of CG in Fm"], 50 | Ycg = rcam_constants[:Ycg], [description="m - y pos of CG in Fm"], 51 | Zcg = rcam_constants[:Zcg], [description="m - z pos of CG in Fm"], 52 | Xac = rcam_constants[:Xac], [description="m - x pos of aerodynamic center in Fm"], 53 | Yac = rcam_constants[:Yac], [description="m - y pos of aerodynamic center in Fm"], 54 | Zac = rcam_constants[:Zac], [description="m - z pos of aerodynamic center in Fm"], 55 | Xapt1 = rcam_constants[:Xapt1], [description="m - x position of engine 1 in Fm"], 56 | Yapt1 = rcam_constants[:Yapt1], [description="m - y position of engine 1 in Fm"], 57 | Zapt1 = rcam_constants[:Zapt1], [description="m - z position of engine 1 in Fm"], 58 | Xapt2 = rcam_constants[:Xapt2], [description="m - x position of engine 2 in Fm"], 59 | Yapt2 = rcam_constants[:Yapt2], [description="m - y position of engine 2 in Fm"], 60 | Zapt2 = rcam_constants[:Zapt2], [description="m - z position of engine 2 in Fm"], 61 | g = rcam_constants[:g], [description="m/s2 - gravity"], 62 | depsda = rcam_constants[:depsda], [description="rad/rad - change in downwash wrt α"], 63 | α_L0 = rcam_constants[:α_L0], [description="rad - zero lift AOA"], 64 | n = rcam_constants[:n], [description="adm - slope of linear region of lift slope"], 65 | a3 = rcam_constants[:a3], [description="adm - coeff of α^3"], 66 | a2 = rcam_constants[:a2], [description="adm - - coeff of α^2"], 67 | a1 = rcam_constants[:a1], [description="adm - - coeff of α^1"], 68 | a0 = rcam_constants[:a0], [description="adm - - coeff of α^0"], 69 | α_switch = rcam_constants[:α_switch], [description="rad - kink point of lift slope"], 70 | ) 71 | 72 | @parameters t 73 | Dt = Differential(t) 74 | 75 | V_b = @variables( 76 | u(t), [description="translational velocity along x-axis [m/s]"], 77 | v(t), [description="translational velocity along y-axis [m/s]"], 78 | w(t), [description="translational velocity along z-axis [m/s]"] 79 | ) 80 | 81 | wbe_b = @variables( 82 | p(t), [description="rotational velocity about x-axis [rad/s]"], 83 | q(t), [description="rotational velocity about y-axis [rad/s]"], 84 | r(t), [description="rotational velocity about z-axis [rad/s]"] 85 | ) 86 | 87 | rot = @variables( 88 | ϕ(t), [description="rotation angle about x-axis/roll or bank angle [rad]"], 89 | θ(t), [description="rotation angle about y-axis/pitch angle [rad]"], 90 | ψ(t), [description="rotation angle about z-axis/yaw angle [rad]"] 91 | ) 92 | 93 | # Controls 94 | U = @variables( 95 | uA(t), [description="aileron [rad]"], 96 | uT(t), [description="tail [rad]"], 97 | uR(t), [description="rudder [rad]"], 98 | uE_1(t), [description="throttle 1 [rad]"], 99 | uE_2(t), [description="throttle 2 [rad]"], 100 | ) 101 | 102 | # Auxiliary Variables to define model. 103 | Auxiliary_vars = @variables Va(t) α(t) β(t) Q(t) CL_wb(t) ϵ(t) α_t(t) CL_t(t) CL(t) CD(t) CY(t) F1(t) F2(t) 104 | 105 | @variables FA_s(t)[1:3] C_bs(t)[1:3,1:3] FA_b(t)[1:3] eta(t)[1:3] dCMdx(t)[1:3, 1:3] dCMdu(t)[1:3, 1:3] CMac_b(t)[1:3] MAac_b(t)[1:3] rcg_b(t)[1:3] rac_b(t)[1:3] MAcg_b(t)[1:3] FE1_b(t)[1:3] FE2_b(t)[1:3] FE_b(t)[1:3] mew1(t)[1:3] mew2(t)[1:3] MEcg1_b(t)[1:3] MEcg2_b(t)[1:3] MEcg_b(t)[1:3] g_b(t)[1:3] Fg_b(t)[1:3] F_b(t)[1:3] Mcg_b(t)[1:3] H_phi(t)[1:3,1:3] 106 | 107 | # Scalarizing all the array variables. 108 | FA_s, C_bs, FA_b, eta, dCMdx, dCMdu, CMac_b, MAac_b, rcg_b, rac_b, MAcg_b, FE1_b, FE2_b, FE_b, mew1, mew2, MEcg1_b, MEcg2_b, MEcg_b, g_b, Fg_b, F_b, Mcg_b, H_phi = collect(FA_s), collect(C_bs), collect(FA_b), collect(eta), collect(dCMdx), collect(dCMdu), collect(CMac_b), collect(MAac_b), collect(rcg_b), collect(rac_b), collect(MAcg_b), collect(FE1_b), collect(FE2_b), collect(FE_b), collect(mew1), collect(mew2), collect(MEcg1_b), collect(MEcg2_b), collect(MEcg_b), collect(g_b), collect(Fg_b), collect(F_b), collect(Mcg_b), collect(H_phi) 109 | 110 | array_vars = vcat(vec(FA_s), vec(C_bs), vec(FA_b), vec(eta), vec(dCMdx), vec(dCMdu), vec(CMac_b), vec(MAac_b), vec(rcg_b), vec(rac_b), vec(MAcg_b), vec(FE1_b), vec(FE2_b), vec(FE_b), vec(mew1), vec(mew2), vec(MEcg1_b), vec(MEcg2_b), vec(MEcg_b), vec(g_b), vec(Fg_b), vec(F_b), vec(Mcg_b), vec(H_phi)) 111 | 112 | eqns =[ 113 | # Step 1. Intermediate variables 114 | # Airspeed 115 | Va ~ sqrt(u^2 + v^2 + w^2) 116 | 117 | # α and β 118 | α ~ atan(w,u) 119 | β ~ asin(v/Va) 120 | 121 | # dynamic pressure 122 | Q ~ 0.5*ρ*Va^2 123 | 124 | 125 | # Step 2. Aerodynamic Force Coefficients 126 | # CL - wing + body 127 | CL_wb ~ n*(α - α_L0) 128 | 129 | # CL thrust 130 | ϵ ~ depsda*(α - α_L0) 131 | α_t ~ α - ϵ + uT + 1.3*q*lt/Va 132 | CL_t ~ 3.1*(St/S) * α_t 133 | 134 | # Total CL 135 | CL ~ CL_wb + CL_t 136 | 137 | # Total CD 138 | CD ~ 0.13 + 0.07 * (n*α + 0.654)^2 139 | 140 | # Total CY 141 | CY ~ -1.6*β + 0.24*uR 142 | 143 | 144 | # Step 3. Dimensional Aerodynamic Forces 145 | # Forces in F_s 146 | FA_s .~ [-CD * Q * S 147 | CY * Q * S 148 | -CL * Q * S] 149 | 150 | 151 | # rotate forces to body axis (F_b) 152 | vec(C_bs .~ [cos(α) 0.0 -sin(α) 153 | 0.0 1.0 0.0 154 | sin(α) 0.0 cos(α)]) 155 | 156 | 157 | FA_b .~ C_bs*FA_s 158 | 159 | # Step 4. Aerodynamic moment coefficients about AC 160 | # moments in F_b 161 | eta .~ [ -1.4 * β 162 | -0.59 - (3.1 * (St * lt) / (S * c̄)) * (α - ϵ) 163 | (1 - α * (180 / (15 * π))) * β 164 | ] 165 | 166 | 167 | vec(dCMdx .~ (c̄ / Va)* [-11.0 0.0 5.0 168 | 0.0 (-4.03 * (St * lt^2) / (S * c̄^2)) 0.0 169 | 1.7 0.0 -11.5]) 170 | 171 | 172 | vec(dCMdu .~ [-0.6 0.0 0.22 173 | 0.0 (-3.1 * (St * lt) / (S * c̄)) 0.0 174 | 0.0 0.0 -0.63]) 175 | 176 | # CM about AC in Fb 177 | CMac_b .~ eta + dCMdx*wbe_b + dCMdu*[uA 178 | uT 179 | uR] 180 | 181 | # Step 5. Aerodynamic moment about AC 182 | # normalize to aerodynamic moment 183 | MAac_b .~ CMac_b * Q * S * c̄ 184 | 185 | # Step 6. Aerodynamic moment about CG 186 | rcg_b .~ [Xcg 187 | Ycg 188 | Zcg] 189 | 190 | rac_b .~ [Xac 191 | Yac 192 | Zac] 193 | 194 | MAcg_b .~ MAac_b + cross(FA_b, rcg_b - rac_b) 195 | 196 | # Step 7. Engine force and moment 197 | # thrust 198 | F1 ~ uE_1 * m * g 199 | F2 ~ uE_2 * m * g 200 | 201 | # thrust vectors (assuming aligned with x axis) 202 | FE1_b .~ [F1 203 | 0 204 | 0] 205 | 206 | FE2_b .~ [F2 207 | 0 208 | 0] 209 | 210 | FE_b .~ FE1_b + FE2_b 211 | 212 | # engine moments 213 | mew1 .~ [Xcg - Xapt1 214 | Yapt1 - Ycg 215 | Zcg - Zapt1] 216 | 217 | mew2 .~ [ Xcg - Xapt2 218 | Yapt2 - Ycg 219 | Zcg - Zapt2] 220 | 221 | MEcg1_b .~ cross(mew1, FE1_b) 222 | MEcg2_b .~ cross(mew2, FE2_b) 223 | 224 | MEcg_b .~ MEcg1_b + MEcg2_b 225 | 226 | # Step 8. Gravity effects 227 | g_b .~ [-g * sin(θ) 228 | g * cos(θ) * sin(ϕ) 229 | g * cos(θ) * cos(ϕ)] 230 | 231 | Fg_b .~ m * g_b 232 | 233 | # Step 9: State derivatives 234 | 235 | # form F_b and calculate u, v, w dot 236 | F_b .~ Fg_b + FE_b + FA_b 237 | 238 | Dt.(V_b) .~ (1 / m)*F_b - cross(wbe_b, V_b) 239 | 240 | # form Mcg_b and calc p, q r dot 241 | Mcg_b .~ MAcg_b + MEcg_b 242 | 243 | Dt.(wbe_b) .~ invIb*(Mcg_b - cross(wbe_b, Ib*wbe_b)) 244 | 245 | # phi, theta, psi dot 246 | vec(H_phi .~ [1.0 sin(ϕ)*tan(θ) cos(ϕ)*tan(θ) 247 | 0.0 cos(ϕ) -sin(ϕ) 248 | 0.0 sin(ϕ)/cos(θ) cos(ϕ)/cos(θ)]) 249 | 250 | Dt.(rot) .~ H_phi*wbe_b 251 | ] 252 | 253 | all_vars = vcat(V_b,wbe_b,rot,U, Auxiliary_vars, array_vars) 254 | 255 | @named rcam_model = ODESystem(eqns, t, all_vars, ps) 256 | 257 | inputs = [uA, uT, uR, uE_1, uE_2] 258 | outputs = [u,v, w, p, q, r, ϕ, θ, ψ] 259 | sys0, diff_idxs0, alge_idxs0, input_idxs0 = ModelingToolkit.io_preprocessing(rcam_model, inputs, outputs) 260 | reduced_states = states(sys0) 261 | sys0 262 | 263 | x0 = Dict( 264 | u => 87.0, 265 | v => 0.0, 266 | w => 1.2713, 267 | p => 0.0, 268 | q => 0.0, 269 | r => 0.0, 270 | ϕ => 0.0, 271 | θ => 0.01495, # approx 5.73 degrees 272 | ψ => 0.0 273 | ) 274 | 275 | u0 = Dict( 276 | uA => 0.0, 277 | uT => -0.1, 278 | uR => 0.0, 279 | uE_1 => 0.08, 280 | uE_2 => 0.08 281 | ) 282 | 283 | # Vector form to keep ordering 284 | x0_vec = map(elem -> float(x0[elem]), reduced_states) 285 | u0_vec = map(elem -> float(u0[elem]), inputs) 286 | 287 | tspan0 = (0.0, 180) 288 | prob0 = ODEProblem(sys0, x0, tspan0, u0, jac = true) 289 | sol0 = solve(prob0, Tsit5()) 290 | plot(sol0, idxs = reduced_states, layout = length(reduced_states)) 291 | 292 | desired_states = Dict(u => 85, v => 0, ϕ => 0, ψ => 0, uE_1 => 0.1, uE_2 => 0.1) 293 | 294 | hard_eq_cons = [ 295 | Va ~ 85 296 | θ - atan(w,u) ~ 0.0 297 | ] 298 | 299 | hard_ineq_cons = [ 300 | - uA + deg2rad(-25) 301 | uA - deg2rad(25) 302 | -uT + deg2rad(-25) 303 | uT - deg2rad(10) 304 | -uR + deg2rad(-30) 305 | uR - deg2rad(30) 306 | -uE_1 + deg2rad(0.5) 307 | uE_1 - deg2rad(10) 308 | -uE_2 + deg2rad(0.5) 309 | uE_2 - deg2rad(10) 310 | ] 311 | 312 | penalty_multipliers = Dict(:desired_states => 10.0, :trim_cons => 10.0, :sys_eqns => 10.0) 313 | 314 | sol_trim, trim_states = trim(rcam_model; penalty_multipliers, desired_states, inputs, hard_eq_cons, hard_ineq_cons) 315 | 316 | x0_trim = Dict(state => trim_states[state] for state in reduced_states) 317 | 318 | u0_trim = Dict(input => trim_states[input] for input in inputs) 319 | 320 | x0_trim_vec = map(elem -> x0_trim[elem], reduced_states) 321 | u0_trim_vec = map(elem -> u0_trim[elem], inputs) 322 | 323 | (; A, B, C, D), ssys = ModelingToolkit.linearize(rcam_model, inputs, outputs; op = merge(x0_trim, u0_trim)) 324 | 325 | linsys = named_ss(ss(A, B, C, D), x=Symbol.(states(ssys)), u=Symbol.(ModelingToolkit.inputs(ssys)), y=Symbol.(ModelingToolkit.outputs(ssys))) 326 | 327 | # Discretization: 328 | Ts = 0.02 329 | disc(x) = c2d(x, Ts) 330 | G = disc(linsys) 331 | 332 | func_sys = JuliaSimControl.build_controlled_dynamics(rcam_model, inputs, outputs; ps) 333 | 334 | pms = ModelingToolkit.varmap_to_vars(ModelingToolkit.defaults(rcam_model), func_sys.p) 335 | 336 | discrete_dynamics = JuliaSimControl.rk4(func_sys, Ts) 337 | 338 | nx = func_sys.nx 339 | ny = func_sys.ny 340 | nu = func_sys.nu 341 | 342 | R1 = Matrix(1e-5*I(nx)) # Dynamics covariance 343 | R2 = Matrix(1e-20*I(ny)) # Measurement covariance 344 | Ru = Matrix(1e-5*I(nu)) 345 | d0 = MvNormal(float.(x0_vec), Matrix(R1)) # Initial point of the simulation x0. 346 | 347 | ukf = JuliaSimControl.UnscentedKalmanFilter(discrete_dynamics, R1, R2, d0); 348 | 349 | op_trim_wrapper = OperatingPoint(x0_trim_vec, u0_trim_vec, x0_trim_vec); 350 | 351 | observer = JuliaSimControl.OperatingPointWrapper(ukf, op_trim_wrapper); 352 | 353 | # 5. Formulate MPC problem: 354 | umin = [ 355 | deg2rad(-25) 356 | deg2rad(-25) 357 | deg2rad(-30) 358 | deg2rad(0.5) 359 | deg2rad(0.5) 360 | ] 361 | 362 | umax = [ 363 | deg2rad(25) 364 | deg2rad(10) 365 | deg2rad(30) 366 | deg2rad(10) 367 | deg2rad(10) 368 | ] 369 | constraints = MPCConstraints(; umin, umax) 370 | 371 | MPC_models = LinearMPCModel(G, observer; constraints, x0 = x0_vec, op=op_trim_wrapper) 372 | 373 | solver = OSQPSolver( 374 | eps_rel = 1e-5, 375 | eps_abs = 1e-5, 376 | max_iter = 500, 377 | check_termination = 5, 378 | sqp_iters = 1, 379 | dynamics_interval = 1, 380 | verbose = false, 381 | polish = false, 382 | ) 383 | 384 | N = 100 # prediction horizon 385 | Q1 = 10I(nx) 386 | Q2 = 1.0*I(nu) 387 | qs = 100 388 | qs2 = 100000 389 | 390 | # The reference is the trim state we want to get to: 391 | MPCProblem = LQMPCProblem(MPC_models; Q1, Q2, qs, qs2, N, r=x0_trim_vec, solver, p = pms) 392 | 393 | # Case 1. MPC - Without disturbance: 394 | @time hist_lin_no_disturb = MPC.solve(MPCProblem; x0 = x0_vec, T = 300, verbose = false, noise=0, dyn_actual=discrete_dynamics, reset_observer = false) 395 | plot(hist_lin_no_disturb, plot_title="Linear MPC", legend=:bottomright, layout=length(x0_vec) + length(u0_vec), sp=[repeat(1:9, outer=2); 10:14], title="", size=(1920, 1080)) 396 | 397 | # Case 2. MPC - With disturbance: 398 | function disturbance_1(u, t) 399 | (nu, ) = size(u) 400 | d0 = zeros(nu) 401 | if t > 100 && t < 140 402 | d0[5] = deg2rad(-40) 403 | end 404 | d0 405 | end 406 | 407 | @time hist_lin_disturbance = MPC.solve(MPCProblem; x0 = x0_vec, T = 300, verbose = false, noise=0, dyn_actual=discrete_dynamics, disturbance = disturbance_1, reset_observer = false) 408 | plot(hist_lin_disturbance, plot_title="Linear MPC", legend=:bottomright, layout=length(x0_vec) + length(u0_vec), sp=[repeat(1:9, outer=2); 10:14], title="", size=(1920, 1080)) 409 | -------------------------------------------------------------------------------- /solutions/problem6.jl: -------------------------------------------------------------------------------- 1 | using DataGeneration, Surrogatize, Visualisations, JSSBase, OrdinaryDiffEq 2 | 3 | Random.seed!(1) 4 | 5 | #Lotkva Volterra 6 | function lv(u, p, t) 7 | u₁, u₂ = u 8 | α, β, γ, δ = p 9 | dx = α * u₁ - β * u₁ * u₂ 10 | dy = δ * u₁ * u₂ - γ * u₂ 11 | [dx, dy] 12 | end 13 | 14 | #Specifying Base ODEProblem 15 | p = [1.75, 1.8, 2.0, 1.8] 16 | u0 = [1.0, 1.0] 17 | tspan = (0.0, 12.5) 18 | prob = ODEProblem{false}(lv, u0, tspan, p) 19 | 20 | 21 | #Number of samples 22 | nsamples_p = 2000 23 | 24 | #Upper and lower bound of parameter space 25 | p_lb = [1.5,1.75,1.5,1.75] 26 | p_ub = [2.5,2.0,2.5,2.0] 27 | 28 | #Setup the sample space to run simulations from 29 | #Only using ParameterSpace but also have ICSpace and CtrlSpace 30 | simconfig = SimulatorConfig(ParameterSpace(p_lb, p_ub, nsamples_p)) 31 | #Run and collect simulations 32 | ed = simconfig(prob; alg = Tsit5()) 33 | 34 | #Basic hyperparameter for the CTESN 35 | RSIZE = 250 36 | model = CTESN(RSIZE) 37 | #Generate surrogate 38 | surrogate = surrogatize(ed, model; verbose = true) 39 | 40 | #visualise data 41 | dashboard_data = generate_dashboard_data(surrogate, ed) 42 | visualise_surrogate_results(dashboard_data) 43 | -------------------------------------------------------------------------------- /solutions/rc.jl: -------------------------------------------------------------------------------- 1 | module RC 2 | 3 | using ModelingToolkit 4 | 5 | @variables t 6 | @connector function Pin(;name) 7 | @variables v(t)=1.0 i(t)=1.0 [connect = Flow] 8 | ODESystem(Equation[], t, [v, i], []; name=name) 9 | end 10 | 11 | function Ground(;name) 12 | @named g = Pin() 13 | eqs = [g.v ~ 0] 14 | compose(ODESystem(eqs, t; name=name), g) 15 | end 16 | 17 | function OnePort(;name) 18 | @named p = Pin() 19 | @named n = Pin() 20 | @variables v(t)=1.0 i(t)=1.0 21 | eqs = [v ~ p.v - n.v, 0 ~ p.i + n.i, i ~ p.i] 22 | compose(ODESystem(eqs, t, [v, i], []; name=name), [p, n]) 23 | end 24 | 25 | function ConstantVoltageSource(;name, V = 1.0) 26 | @named oneport = OnePort() 27 | @unpack v = oneport 28 | @parameters V = V 29 | eqs = [v ~ V] 30 | extend(ODESystem(eqs, t; name=name), oneport) 31 | end 32 | 33 | function CustomVoltageSource(;name, V) 34 | @named oneport = OnePort() 35 | @unpack v = oneport 36 | eqs = [v ~ V(t)] 37 | extend(ODESystem(eqs, t; name=name), oneport) 38 | end 39 | 40 | function Capacitor(;name, C = 1.0) 41 | @named oneport = OnePort() 42 | @unpack v, i = oneport 43 | @parameters C = C 44 | D = Differential(t) 45 | eqs = [D(v) ~ i / C] 46 | extend(ODESystem(eqs, t; name=name), oneport) 47 | end 48 | function Resistor(;name, R = 1.0) 49 | @named oneport = OnePort() 50 | @unpack v, i = oneport 51 | @parameters R = R 52 | eqs = [v ~ i * R] 53 | extend(ODESystem(eqs, t; name=name), oneport) 54 | end 55 | 56 | 57 | 58 | end # module 59 | 60 | using ModelingToolkit, Plots, DifferentialEquations, .RC 61 | t = RC.t 62 | @named source = RC.ConstantVoltageSource(V = 1.0) 63 | @named resistor = RC.Resistor() 64 | @named capacitor = RC.Capacitor() 65 | @named ground = RC.Ground() 66 | eqs = [connect(source.p, resistor.p), 67 | connect(resistor.n, capacitor.p), 68 | connect(capacitor.n, ground.g), 69 | connect(capacitor.n, source.n)] 70 | @named model = ODESystem(eqs, t) 71 | model = compose(model, [source, resistor, capacitor, ground]) 72 | sys = structural_simplify(model) 73 | prob = ODEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) 74 | sol = solve(prob) 75 | plot(sol) 76 | 77 | source_fun = t -> sum(x->sin(x * t), (1, 2, 10, 100)) 78 | 79 | @named source = RC.CustomVoltageSource(V = source_fun) 80 | @named resistor = RC.Resistor() 81 | @named capacitor = RC.Capacitor() 82 | @named ground = RC.Ground() 83 | eqs = [connect(source.p, resistor.p), 84 | connect(resistor.n, capacitor.p), 85 | connect(capacitor.n, ground.g), 86 | connect(capacitor.n, source.n)] 87 | @named model = ODESystem(eqs, t) 88 | model = compose(model, [source, resistor, capacitor, ground]) 89 | sys = structural_simplify(model) 90 | prob = ODEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) 91 | sol = solve(prob) 92 | ts = range(0, 10, length=5000) 93 | p1 = plot(sol, lab = "v(t)") 94 | p2 = plot(ts, source_fun.(ts), label="source") 95 | plot(p1, p2, layout=(2,1)) 96 | --------------------------------------------------------------------------------