├── test └── runtests.jl ├── src └── ModelingToolkitCourse.jl ├── docs ├── src │ ├── img │ │ ├── dp5.png │ │ ├── mass.png │ │ ├── System1.png │ │ ├── System2.png │ │ ├── System3.png │ │ ├── dp5_opt.png │ │ ├── minstages.png │ │ ├── rk_tableau.png │ │ ├── tsit5_opt.png │ │ ├── volume_eq1.png │ │ ├── volume_eq2.png │ │ ├── rooted_tree.png │ │ ├── tsit5_coeffs.png │ │ ├── double_pendulum.mp4 │ │ ├── MassSpringDamper.png │ │ ├── geometric_series.png │ │ ├── taylor_remainder.png │ │ ├── num_order_conditions.png │ │ ├── numerical_stiffness_effect.png │ │ ├── force_input.svg │ │ ├── mass.svg │ │ ├── reference.svg │ │ ├── MassChange.svg │ │ ├── damper1.svg │ │ ├── spring_damper.svg │ │ ├── VolumeChange.svg │ │ ├── spring.svg │ │ ├── damper.svg │ │ ├── Example.svg │ │ ├── momentum_balance.svg │ │ └── ports.svg │ ├── lectures │ │ ├── run_away_train.mp4 │ │ ├── run_away_train_slow.mp4 │ │ ├── volume.jl │ │ ├── lecture2.jl │ │ ├── run_away_train_utils.jl │ │ ├── run_away_train_hydraulic.jl │ │ ├── lecture1.jl │ │ ├── lecture2.md │ │ └── lecture6.md │ └── index.md ├── Project.toml └── make.jl ├── .vscode └── settings.json ├── Project.toml ├── .github ├── workflows │ ├── SpellCheck.yml │ ├── CompatHelper.yml │ └── Documentation.yml └── dependabot.yml ├── .gitignore ├── LICENSE └── README.md /test/runtests.jl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ModelingToolkitCourse.jl: -------------------------------------------------------------------------------- 1 | module ModelingToolkitCourse 2 | 3 | end 4 | -------------------------------------------------------------------------------- /docs/src/img/dp5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/dp5.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "julia.environmentPath": "c:\\Work\\MITCourse\\ModelingToolkitCourse" 3 | } -------------------------------------------------------------------------------- /docs/src/img/mass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/mass.png -------------------------------------------------------------------------------- /docs/src/img/System1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/System1.png -------------------------------------------------------------------------------- /docs/src/img/System2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/System2.png -------------------------------------------------------------------------------- /docs/src/img/System3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/System3.png -------------------------------------------------------------------------------- /docs/src/img/dp5_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/dp5_opt.png -------------------------------------------------------------------------------- /docs/src/img/minstages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/minstages.png -------------------------------------------------------------------------------- /docs/src/img/rk_tableau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/rk_tableau.png -------------------------------------------------------------------------------- /docs/src/img/tsit5_opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/tsit5_opt.png -------------------------------------------------------------------------------- /docs/src/img/volume_eq1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/volume_eq1.png -------------------------------------------------------------------------------- /docs/src/img/volume_eq2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/volume_eq2.png -------------------------------------------------------------------------------- /docs/src/img/rooted_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/rooted_tree.png -------------------------------------------------------------------------------- /docs/src/img/tsit5_coeffs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/tsit5_coeffs.png -------------------------------------------------------------------------------- /docs/src/img/double_pendulum.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/double_pendulum.mp4 -------------------------------------------------------------------------------- /docs/src/img/MassSpringDamper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/MassSpringDamper.png -------------------------------------------------------------------------------- /docs/src/img/geometric_series.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/geometric_series.png -------------------------------------------------------------------------------- /docs/src/img/taylor_remainder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/taylor_remainder.png -------------------------------------------------------------------------------- /docs/src/img/num_order_conditions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/num_order_conditions.png -------------------------------------------------------------------------------- /docs/src/lectures/run_away_train.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/lectures/run_away_train.mp4 -------------------------------------------------------------------------------- /docs/src/lectures/run_away_train_slow.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/lectures/run_away_train_slow.mp4 -------------------------------------------------------------------------------- /docs/src/img/numerical_stiffness_effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SciML/ModelingToolkitCourse/HEAD/docs/src/img/numerical_stiffness_effect.png -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "ModelingToolkitCourse" 2 | uuid = "d0ecabdd-fe99-481e-9814-e16fa9c541df" 3 | authors = ["Chris Rackauckas "] 4 | version = "1.0.0" 5 | 6 | [deps] 7 | ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" 8 | 9 | [compat] 10 | ModelingToolkit = "8, 9" 11 | -------------------------------------------------------------------------------- /.github/workflows/SpellCheck.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | typos-check: 7 | name: Spell Check with Typos 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout Actions Repository 11 | uses: actions/checkout@v6 12 | - name: Check spelling 13 | uses: crate-ci/typos@v1.16.23 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" # Location of package manifests 6 | schedule: 7 | interval: "weekly" 8 | ignore: 9 | - dependency-name: "crate-ci/typos" 10 | update-types: ["version-update:semver-patch", "version-update:semver-minor"] -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | 3 | on: 4 | schedule: 5 | - cron: '00 * * * *' 6 | issues: 7 | types: [opened, reopened] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | julia-version: [1] 15 | julia-arch: [x86] 16 | os: [ubuntu-latest] 17 | steps: 18 | - uses: julia-actions/setup-julia@latest 19 | with: 20 | version: ${{ matrix.julia-version }} 21 | - name: Pkg.add("CompatHelper") 22 | run: julia -e 'using Pkg; Pkg.add("CompatHelper")' 23 | - name: CompatHelper.main() 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | run: julia -e 'using CompatHelper; CompatHelper.main(;subdirs=["", "docs"])' 27 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" 3 | DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" 4 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 5 | ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" 6 | ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" 7 | ModelingToolkitCourse = "d0ecabdd-fe99-481e-9814-e16fa9c541df" 8 | ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" 9 | OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" 10 | Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" 11 | Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" 12 | Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" 13 | Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" 14 | 15 | [compat] 16 | DataInterpolations = "5, 6" 17 | DifferentialEquations = "7" 18 | Documenter = "1" 19 | ForwardDiff = "0.10" 20 | ModelingToolkit = "9" 21 | ModelingToolkitStandardLibrary = "2" 22 | OrdinaryDiffEq = "=6.74.1" 23 | Plots = "1" 24 | Setfield = "1" 25 | Sundials = "4" 26 | Symbolics = "5, 6" 27 | -------------------------------------------------------------------------------- /.github/workflows/Documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'release-' 8 | tags: '*' 9 | pull_request: 10 | schedule: 11 | - cron: '9 16 * * 3' 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v6 17 | - uses: julia-actions/setup-julia@latest 18 | with: 19 | version: '1' 20 | - name: Install dependencies 21 | run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' 22 | - name: Build and deploy 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token 25 | DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key 26 | run: julia --project=docs/ --code-coverage=user docs/make.jl 27 | - uses: julia-actions/julia-processcoverage@v1 28 | - uses: codecov/codecov-action@v5 29 | with: 30 | files: lcov.info 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SciML Open Source Scientific Machine Learning 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 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, ModelingToolkitCourse 2 | # NOTE: OrdinaryDiffEq limited to v6.74.1 because of bug https://github.com/SciML/OrdinaryDiffEq.jl/issues/2250 3 | 4 | pages = [ 5 | "Home" => "index.md", 6 | "lectures/lecture1.md", 7 | "lectures/lecture2.md", 8 | "lectures/lecture3.md", 9 | "lectures/lecture4.md", 10 | "lectures/lecture6.md", 11 | "lectures/lecture7.md", 12 | "lectures/lecture8.md", 13 | ] 14 | 15 | ENV["GKSwstype"] = "100" 16 | using Plots 17 | 18 | makedocs(sitename = "ModelingToolkit Course", 19 | authors = "Chris Rackauckas", 20 | modules = [ModelingToolkitCourse], 21 | clean = true, doctest = false, linkcheck = true, 22 | linkcheck_ignore = ["https://epubs.siam.org/doi/10.1137/0903023", 23 | "https://link.springer.com/book/10.1007/978-3-642-05221-7", 24 | "http://www.siam.org/journals/auth-info.php"], 25 | format = Documenter.HTML(assets = ["assets/favicon.ico"], 26 | canonical = "https://docs.sciml.ai/ModelingToolkitCourse/stable/"), 27 | pages = pages) 28 | 29 | #= 30 | using LiveServer 31 | serve(dir="build") 32 | =# 33 | 34 | deploydocs(repo = "github.com/SciML/ModelingToolkitCourse.git"; 35 | push_preview = true) 36 | -------------------------------------------------------------------------------- /docs/src/lectures/volume.jl: -------------------------------------------------------------------------------- 1 | using ModelingToolkit 2 | using ModelingToolkitStandardLibrary.Mechanical.Translational 3 | using ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible 4 | 5 | using ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible: liquid_density 6 | 7 | 8 | 9 | @component function Volume(; 10 | #initial conditions 11 | x, 12 | dx=0, 13 | p, 14 | drho=0, 15 | dm=0, 16 | 17 | #parameters 18 | area, 19 | direction=+1, name) 20 | pars = @parameters begin 21 | area = area 22 | end 23 | 24 | vars = @variables begin 25 | x(t) = x 26 | dx(t) = dx 27 | p(t) = p 28 | f(t) = p * area 29 | rho(t) 30 | drho(t) = drho 31 | dm(t) = dm 32 | end 33 | 34 | systems = @named begin 35 | port = HydraulicPort(; p_int=p) 36 | flange = MechanicalPort(; f, v=dx) 37 | end 38 | 39 | eqs = [ 40 | # connectors 41 | port.p ~ p 42 | port.dm ~ dm 43 | flange.v * direction ~ dx 44 | flange.f * direction ~ -f 45 | 46 | # differentials 47 | D(x) ~ dx 48 | D(rho) ~ drho 49 | 50 | # physics 51 | rho ~ liquid_density(port, p) 52 | f ~ p * area 53 | dm ~ drho * x * area + rho * dx * area] 54 | 55 | ODESystem(eqs, t, vars, pars; name, systems, defaults=[rho => liquid_density(port)]) 56 | end -------------------------------------------------------------------------------- /docs/src/lectures/lecture2.jl: -------------------------------------------------------------------------------- 1 | using ModelingToolkit 2 | using DifferentialEquations 3 | using Symbolics 4 | using Plots 5 | using ModelingToolkit: t_nounits as t, D_nounits as D 6 | 7 | # parameters ------- 8 | pars = @parameters begin 9 | r₀ = 876 #kg/s 10 | β = 1.2e9 #Pa 11 | A = 0.01 #m² 12 | x₀ = 1.0 #m 13 | M = 10_000 #kg 14 | g = 9.807 #m/s² 15 | amp = 5e-2 #m 16 | f = 15 #Hz 17 | end 18 | 19 | dt = 1e-4 #s 20 | t_end = 0.2 #s 21 | time = 0:dt:t_end 22 | 23 | x_fun(t,amp,f) = amp*sin(2π*t*f) + x₀ 24 | ẋ_fun = build_function(expand_derivatives(D(x_fun(t,amp,f))), t, amp, f; expression=false) 25 | 26 | 27 | vars = @variables begin 28 | x(t) # = x₀ 29 | ẋ(t) 30 | ẍ(t) 31 | p(t) # = m*g/A #Pa 32 | ṁ(t) 33 | r(t) 34 | ṙ(t) 35 | end 36 | 37 | function get_base_equations(density_type) 38 | 39 | eqs = [ 40 | D(x) ~ ẋ 41 | D(ẋ) ~ ẍ 42 | D(r) ~ ṙ 43 | 44 | r ~ r₀*(1 + p/β) 45 | 46 | ṁ ~ ṙ*x*A + (density_type)*ẋ*A 47 | M*ẍ ~ p*A - M*g 48 | ] 49 | 50 | return eqs 51 | end 52 | 53 | eqs_ṁ1 = [ 54 | get_base_equations(r₀)... 55 | ṁ ~ ẋ_fun(t,amp,f)*A*r 56 | ] 57 | 58 | eqs_ṁ2 = [ 59 | get_base_equations(r)... 60 | ṁ ~ ẋ_fun(t,amp,f)*A*r 61 | ] 62 | 63 | eqs_x = [ 64 | get_base_equations(r)... 65 | x ~ x_fun(t,amp,f) 66 | ] 67 | 68 | @mtkbuild odesys_x = ODESystem(eqs_x, t, vars, pars) 69 | prob_x = ODEProblem(sys_x, [], (0, t_end)) 70 | sol_x = solve(prob_x; saveat=time) 71 | 72 | @mtkbuild odesys_ṁ1 = ODESystem(eqs_ṁ1, t, vars, pars) 73 | u0 = [sol_x[s][1] for s in unknowns(sys_ṁ1)] 74 | prob_ṁ1 = ODEProblem(sys_ṁ1, u0, (0, t_end)) 75 | sol_ṁ1 = solve(prob_ṁ1) 76 | 77 | plot(sol_ṁ1; idxs=ṁ, label="guess", ylabel="ṁ") 78 | plot!(sol_x; idxs=ṁ, label="solution") 79 | 80 | @mtkbuild odesys_ṁ2 = ODESystem(eqs_ṁ2, t, vars, pars) 81 | prob_ṁ2 = ODEProblem(sys_ṁ2, u0, (0, t_end)) 82 | sol_ṁ2 = solve(prob_ṁ2) 83 | 84 | plot(sol_x; idxs=x, label="solution", ylabel="x") 85 | plot!(sol_ṁ1; idxs=x, label="case 1") 86 | plot!(sol_ṁ2; idxs=x, label="case 2") 87 | 88 | plot(time, (sol_ṁ1(time)[x] .- sol_ṁ2(time)[x])/1e-3, ylabel="x error [mm]", xlabel="t [s]") 89 | 90 | # ----------------------------------------------------- 91 | 92 | using DataInterpolations 93 | mass_flow_fun = LinearInterpolation(sol_x[ṁ], sol_x.t) 94 | 95 | open("dm.jl","w") do io 96 | print(io, "u = [") 97 | join(io, sol_x[ṁ], ',') 98 | print(io, "]") 99 | end 100 | 101 | 102 | plot(sol_x; idxs=ṁ) 103 | plot!(time, -263.9489641054512*cos.(2π*time*15)) 104 | 105 | import ModelingToolkitStandardLibrary.Mechanical.Translational as T 106 | import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC 107 | import ModelingToolkitStandardLibrary.Blocks as B 108 | 109 | function MassVolume(; name, dx, drho, dm) 110 | 111 | pars = @parameters begin 112 | A = 0.01 #m² 113 | x₀ = 1.0 #m 114 | M = 10_000 #kg 115 | g = 9.807 #m/s² 116 | amp = 5e-2 #m 117 | f = 15 #Hz 118 | p_int=M*g/A 119 | dx=dx 120 | drho=drho 121 | dm=dm 122 | end 123 | vars = [] 124 | systems = @named begin 125 | fluid = IC.HydraulicFluid(; density = 876, bulk_modulus = 1.2e9) 126 | mass = T.Mass(;v=dx,m=M,g=-g) 127 | vol = IC.Volume(;area=A, x=x₀, p=p_int, dx, drho, dm) 128 | mass_flow = IC.MassFlow(;p_int) 129 | mass_flow_input = B.TimeVaryingFunction(;f = mass_flow_fun) 130 | end 131 | 132 | eqs = [ 133 | connect(mass.flange, vol.flange) 134 | connect(vol.port, mass_flow.port) 135 | connect(mass_flow.dm, mass_flow_input.output) 136 | connect(mass_flow.port, fluid) 137 | ] 138 | 139 | return ODESystem(eqs, t, vars, pars; systems, name) 140 | end 141 | 142 | dx = sol_x[ẋ][1] 143 | drho = sol_x[ṙ][1] 144 | dm = sol_x[ṁ][1] 145 | 146 | @named odesys = MassVolume(; dx, drho, dm) 147 | # using DAE2AE 148 | sys = structural_simplify(odesys) 149 | # sys = no_simplify(expand_connections(odesys)) |> complete 150 | prob = ODEProblem(sys, [], (0, t_end)) 151 | sol=solve(prob) 152 | plot(sol; idxs=sys.vol.x, linewidth=2) 153 | plot!(sol_x; idxs=x) 154 | 155 | plot(sol; idxs=sys.vol.dx, linewidth=2) 156 | plot!(sol_x; idxs=ẋ) 157 | 158 | 159 | 160 | du0 = [ 161 | 0 # mass₊v(t) 162 | sol_x[ẋ][1] # vol₊x(t) 163 | sol_x[ẋ][1] # vol₊moving_volume₊x(t) 164 | sol_x[ṙ][1] # vol₊moving_volume₊rho(t) 165 | sol_x[ṙ*β/r₀][1] # vol₊moving_volume₊port₊p(t) 166 | sol_x[ṙ*β/r₀][1] # vol₊damper₊port_b₊p(t) 167 | 0 # vol₊moving_volume₊drho(t) 168 | ] 169 | prob = DAEProblem(sys, du0, [], (0, t_end)) 170 | sol = solve(prob, DImplicitEuler(nlsolve=NLNewton(check_div=false))) 171 | 172 | prob = ODEProblem(sys, [], (0, t_end)) 173 | sol = solve(prob, ImplicitEuler(nlsolve=NLNewton(check_div=false, max_iter=100, relax=4//10)); dt, adaptive=false) 174 | 175 | using SimpleEuler 176 | sol = solve(prob, BackwardEuler(); dt, adaptive=false) 177 | 178 | plot(sol; idxs=sys.vol.x, linewidth=2) 179 | plot!(sol_x; idxs=x) 180 | 181 | plot(sol; idxs=sys.mass_flow.dm.u) 182 | 183 | plot(sol; idxs=sys.vol.moving_volume.port.p) 184 | -------------------------------------------------------------------------------- /docs/src/img/force_input.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 55 | 56 | 58 | 71 | 76 | 77 | 90 | 95 | 96 | 97 | 102 | 109 | 114 | 118 | v 129 | 134 | f 145 | 149 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /docs/src/img/mass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 87 | 92 | 93 | 94 | 99 | 106 | 110 | 114 | v 125 | 130 | f 141 | port orientation 152 | component orientation 163 | 164 | 165 | -------------------------------------------------------------------------------- /docs/src/img/reference.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 87 | 92 | 93 | 94 | 99 | 103 | 110 | f 121 | 126 | 130 | 134 | 138 | 142 | 146 | 150 | 154 | 158 | v 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/src/lectures/run_away_train_utils.jl: -------------------------------------------------------------------------------- 1 | using CairoMakie 2 | using Makie.GeometryBasics 3 | 4 | function plot_train(sol, sys, tx=0.0) 5 | tm = Observable(tx) 6 | idx = Dict(reverse.(enumerate(unknowns(sys)))) 7 | 8 | fig = Figure() 9 | a = Axis(fig[1,1], aspect=DataAspect(), ) 10 | hidedecorations!(a) 11 | slt(t,idxs) = sol(t; idxs) # =[sys.car1.x_a, sys.car1.x_b, sys.car2.x_a, sys.car2.x_b, sys.engine.x_a, sys.engine.x_b, sys.stopper.spring.x+sys.engine.barrier]) 12 | 13 | c1xa(t) = slt(t, sys.car1.x_a) 14 | c1xb(t) = slt(t, sys.car1.x_b) 15 | 16 | flb1(t) = (c1xa(t), 0) 17 | frb1(t) = (c1xb(t), 0) 18 | frt1(t) = (c1xb(t), 0.25) 19 | delta1(t) = c1xb(t)-c1xa(t) 20 | flt1a(t) = (c1xb(t)-delta1(t)*0.75, 0.25) 21 | flt1b(t) = (c1xa(t), 0.1) 22 | 23 | ply1(t) = Polygon(Point2f[flb1(t), frb1(t), frt1(t), flt1a(t), flt1b(t)]) 24 | poly!(a, lift(ply1, tm), color=:blue) 25 | 26 | c2xa(t) = slt(t, sys.car2.x_a) 27 | c2xb(t) = slt(t, sys.car2.x_b) 28 | 29 | flb2(t) = (c2xa(t), 0) 30 | frb2(t) = (c2xb(t), 0) 31 | frt2(t) = (c2xb(t), 0.25) 32 | flt2(t) = (c2xa(t), 0.25) 33 | 34 | ply2(t) = Polygon(Point2f[flb2(t), frb2(t), frt2(t), flt2(t)]) 35 | poly!(a, lift(ply2, tm), color=:blue) 36 | 37 | 38 | c3xa(t) = slt(t, sys.car3.x_a) 39 | c3xb(t) = slt(t, sys.car3.x_b) 40 | 41 | flb3(t) = (c3xa(t), 0) 42 | frb3(t) = (c3xb(t), 0) 43 | frt3(t) = (c3xb(t), 0.25) 44 | flt3(t) = (c3xa(t), 0.25) 45 | 46 | ply3(t) = Polygon(Point2f[flb3(t), frb3(t), frt3(t), flt3(t)]) 47 | poly!(a, lift(ply3, tm), color=:blue) 48 | 49 | 50 | 51 | exa(t) = slt(t, sys.engine.x_a) 52 | exb(t) = slt(t, sys.engine.x_b) 53 | 54 | flbe(t) = (exa(t), 0) 55 | frbe(t) = (exb(t), 0) 56 | deltae(t) = exb(t)-exa(t) 57 | frtea(t) = (exb(t), 0.1) 58 | frteb(t) = (exa(t)+deltae(t)*0.75, 0.25) 59 | flte(t) = (exa(t), 0.25) 60 | 61 | plye(t) = Polygon(Point2f[flbe(t), frbe(t), frtea(t), frteb(t), flte(t)]) 62 | poly!(a, lift(plye, tm), color=:blue) 63 | 64 | 65 | br(t) = slt(t, (5-sys.stopper.volume.x)+10) 66 | brp(t) = Polygon(Point2f[ 67 | (br(t),0) 68 | (br(t)+0.1,0) 69 | (br(t)+0.1,0.1) 70 | (br(t)+0.5,0.1) 71 | (br(t)+0.5,0.2) 72 | (br(t)+0.1,0.2) 73 | (br(t)+0.1,0.3) 74 | (br(t),0.3) 75 | ]) 76 | 77 | poly!(a, lift(brp, tm), color=(:red, 0.5)) 78 | 79 | scaling(t,p) = (10+log((slt(t,p)/1e5+0.01)/100))/10 80 | 81 | v5(t) = slt(t, 10-sys.stopper.volume.v5.x) 82 | v5c(t) = (:red, scaling(t, sys.stopper.volume.v5.port.p) ) 83 | v5p(t) = Polygon(Point2f[ 84 | (v5(t),0) 85 | (11,0) 86 | (11,0.3) 87 | (v5(t),0.3) 88 | ]) 89 | poly!(a, lift(v5p, tm); color=lift(v5c, tm)) #, colormap = :jet, colorrange = (1, 20)) 90 | 91 | 92 | v4(t) = slt(t, 10+1-sys.stopper.volume.v4.x) 93 | v4c(t) = (:red, scaling(t, sys.stopper.volume.v4.port.p) ) 94 | v4p(t) = Polygon(Point2f[ 95 | (v4(t),0) 96 | (12,0) 97 | (12,0.3) 98 | (v4(t),0.3) 99 | ]) 100 | poly!(a, lift(v4p, tm); color=lift(v4c, tm)) #, colormap = :jet, colorrange = (1, 20)) 101 | 102 | 103 | v3(t) = slt(t, 10+2-sys.stopper.volume.v3.x) 104 | v3c(t) = (:red, scaling(t, sys.stopper.volume.v3.port.p) ) 105 | v3p(t) = Polygon(Point2f[ 106 | (v3(t),0) 107 | (13,0) 108 | (13,0.3) 109 | (v3(t),0.3) 110 | ]) 111 | poly!(a, lift(v3p, tm); color=lift(v3c, tm)) #, colormap = :jet, colorrange = (1, 20)) 112 | 113 | v2(t) = slt(t, 10+3-sys.stopper.volume.v2.x) 114 | v2c(t) = (:red, scaling(t, sys.stopper.volume.v2.port.p) ) 115 | v2p(t) = Polygon(Point2f[ 116 | (v2(t),0) 117 | (14,0) 118 | (14,0.3) 119 | (v2(t),0.3) 120 | ]) 121 | poly!(a, lift(v2p, tm); color=lift(v2c, tm)) #, colormap = :jet, colorrange = (1, 20)) 122 | 123 | v1(t) = slt(t, 10+4-sys.stopper.volume.v1.x) 124 | v1c(t) = (:red, scaling(t, sys.stopper.volume.v1.port.p) ) 125 | v1p(t) = Polygon(Point2f[ 126 | (v1(t),0) 127 | (15,0) 128 | (15,0.3) 129 | (v1(t),0.3) 130 | ]) 131 | poly!(a, lift(v1p, tm); color=lift(v1c, tm)) #, colormap = :jet, colorrange = (1, 20)) 132 | 133 | # track 134 | lines!(a, [0,15], [-0.05,-0.05]; linewidth=1, color=:black) 135 | 136 | 137 | CairoMakie.ylims!(a, -5, 5) 138 | CairoMakie.xlims!(a, 0, 15) 139 | fig, tm 140 | end 141 | 142 | # fig,tm = plot_train(sol, sys); fig 143 | # fig, = plot_train(sol, sys, 0.76); fig 144 | 145 | function record_train(fig, tm; start, stop, step, framerate, filename) 146 | 147 | timestamps = range(start, stop; step) 148 | 149 | record(fig, filename, timestamps; 150 | framerate) do t 151 | tm[] = t 152 | end 153 | 154 | nothing 155 | end 156 | 157 | fig, tm = plot_train(sol, sys) 158 | record_train(fig, tm; start=0.72, stop=0.729, step=1e-4, framerate=20, filename="run_away_train_slow.mp4") 159 | record_train(fig, tm; start=0.0, stop=5, step=1/30, framerate=30, filename="run_away_train.mp4") 160 | 161 | # plot(sol; idxs=sys.stopper.volume.moving_volume.port.p/1e5, xlims=(0.7, 0.9)) 162 | # plot!(sol; idxs=sys.stopper.volume.v1.port.p/1e5, xlims=(0.7, 0.9)) 163 | # plot!(sol; idxs=sys.stopper.volume.v5.port.p/1e5, xlims=(0.7, 0.9)) 164 | 165 | # plot(sol; idxs=sys.stopper.volume.moving_volume.dx, xlims=(0.7, 0.9)) 166 | # plot!(sol; idxs=sys.stopper.volume.v1.dx, xlims=(0.7, 0.9)) 167 | # plot!(sol; idxs=sys.stopper.volume.v5.dx, xlims=(0.7, 0.9)) -------------------------------------------------------------------------------- /docs/src/img/MassChange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 30 | 35 | 36 | 43 | 49 | 50 | 51 | 72 | 81 | 82 | 84 | 85 | 87 | image/svg+xml 88 | 90 | 91 | 92 | 93 | 98 | 104 | 110 | 116 | 122 | 128 | qin 141 | qout 154 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/src/img/damper1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 87 | 92 | 93 | 94 | 99 | 103 | 107 | 111 | 118 | f 129 | 134 | 138 | 142 | 146 | 150 | 154 | 158 | 162 | 166 | v 177 | 178 | 179 | -------------------------------------------------------------------------------- /docs/src/img/spring_damper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 75 | 80 | 84 | 88 | 92 | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | 128 | 132 | 136 | 140 | 144 | F 155 | k 166 | d 177 | 181 | x 192 | 193 | 194 | -------------------------------------------------------------------------------- /docs/src/img/VolumeChange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 30 | 35 | 36 | 43 | 49 | 50 | 51 | 72 | 81 | 82 | 84 | 85 | 87 | image/svg+xml 88 | 90 | 91 | 92 | 93 | 98 | 103 | 108 | 114 | 120 | 126 | 132 | V0 145 | P0 158 | V 171 | P 184 | 189 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /docs/src/img/spring.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 87 | 92 | 93 | 106 | 111 | 112 | 125 | 130 | 131 | 132 | 137 | 144 | 151 | a 162 | b 173 | f 184 | 189 | 194 | 198 | 202 | v 213 | v 224 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # ModelingToolkit Course: Composable System Modeling and Its Compilation 2 | 3 | ## 18.S191 Special Subject in Mathematics: Composable System Modeling and Its Compilation 4 | 5 | Traditionally, modeling physical systems often requires a deep understanding of the physics and equations of motions or states, simplifying the differential equations using conservation laws and constraints, and finally implementing simplified equations in a scientific computing language to numerically solve them. However, this workflow is tedious and not expressive. A simple change in the underlying physical system often requires a complete re-derivation of the simplified equations. A composable modeling system frees domain experts from the time-consuming derivation, simplification, and implementation by allowing them to model each physical component separately and hierarchically, thereby enabling them to build more accurate and complex models without compromising the simulation performance. In this course, we will dive into the practice of implementing composable physical models and the compilation process of the model system using the ModelingToolkit.jl acausal modeling framework in Julia. Students will learn the mathematics and numerical methods behind solving industry-scale models, covering topics such as differential-algebraic equations (DAEs), modern techniques in implicit integrators (backwards differentiation formulae (BDFs)), symbolic manipulation of equations via techniques like Pantelides algorithm and tearing of nonlinear systems, and more. Applications for solving real-world problems like modeling battery systems of electric vehicles, efficient control of hydraulic and HVAC systems, and more will be used to demonstrate how these techniques are used in industrial settings. 6 | 7 | ## Syllabus 8 | 9 | Prerequisites: While this course will be mixing ideas from symbolic computing, numerical differential equations, and topics from mechanical engineering, no one in the course is expected to have covered all of these topics before. Understanding of calculus, linear algebra, and programming is essential. The course is considered self-contained starting from the basic building blocks of undergraduate differential equations. Problem sets will involve use of Julia, a Matlab-like environment (little or no prior experience required; you will learn as you go), for doing acausal modeling via the ModelingToolkit.jl system. 10 | 11 | Textbook & Other Reading: There is no textbook for this course. For a textbook that covers the practical parts of doing modeling and simulation in an acausal way, Michael Tiller's "Modelica by Example" is a good reference (see https://mbe.modelica.university/). For a textbook that covers the algorithms of acausal modeling compilers, there is no recommended textbook and lecture notes will be supplied as a primary source. 12 | 13 | Grading: The final project proposal (due January 15th) is 25%, and 75% for the final project (due February 2nd). Final projects will be submitted electronically via email. 14 | 15 | ## Final Project 16 | 17 | The final project can take two forms: 18 | 19 | 1. Developing an acausal model of some real-world system. 20 | 2. Implementation and analysis of a new acausal modeling compiler feature. 21 | 3. Implementation and analysis of numerical methods for acausal models. 22 | 23 | A final project proposal is due January 19th and the final project is due on the last day of the course. The last day will be final project presentations where the work is demonstrated to the class. 24 | 25 | The final project's deliverable can take two different forms: 26 | 27 | 1. A final project writeup: a 5-10 page paper using the style template from the [_SIAM Journal on Numerical Analysis_](http://www.siam.org/journals/auth-info.php) (or similar) explaining what was done, along with a Github repository package with the components of the model and docs/tests which demonstrate the successful composed model. 28 | 2. A pull request to one of the libraries (ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq, NonlinearSolve, etc.). For this version of the project, it is sufficient to supply a pull request to MTK/MSL with a description of the feature being implemented, tests of the transformation, and documentation showcasing its correct action on test models. 29 | 30 | We expect the work to be roughly the same for the two routes, where the 1st would entail more theory and mathematical writeup while the latter is more focused on code and documentation. Note that any project considering doing a new acausal modeling feature should heavily consider doing the pull request route as writing a toy acausal modeling compiler within the timeframe of the course is likely to be unsuccessful. 31 | 32 | Note that many of these projects are starter projects towards publications. If you're interested in continuing this work after the IAP towards a publication, please discuss during the project selection page so the project can be appropriately scoped. 33 | 34 | ### Project Type 1 Ideas: Developing an Acausal Model of A Real-World System 35 | 36 | The following sources can be used as inspiration: 37 | 38 | * Modelica "other" libraries (https://modelica.org/libraries/) 39 | * Modelica Standard Library (https://github.com/modelica/ModelicaStandardLibrary) 40 | 41 | 42 | ### Project Type 2 Ideas: Implementation and analysis of a new acausal modeling compiler feature 43 | 44 | * Automated Laplace and Fourier transforms 45 | * Automated function transformation of observables (i.e. log-transform states to enforce positivity) 46 | * Symbolic generation of sensitivity analysis equations (https://github.com/SciML/ModelingToolkit.jl/issues/39) 47 | * Lamperti transformation of stochastic differential equations (https://github.com/SciML/ModelingToolkit.jl/issues/140) 48 | * Automated conversion of distributed delay equations into ODEs (https://github.com/SciML/ModelingToolkit.jl/issues/45) 49 | * Specialized nonlinear solvers based on strongly connected components 50 | * Inline integration (https://people.inf.ethz.ch/fcellier/Pubs/OO/esm_95.pdf) 51 | * Automated detection of events from discontinuities in the ODE/DAE definition 52 | * Polynomial chaos expansions for fast uncertainty quantification 53 | * DCP on OptimizationSystem to automatically transform nonlinear optimization problems to convex optimization problems (http://cvxr.com/cvx/doc/dcp.html) 54 | * Common subexpression elimination in Symbolic code generation 55 | * Extendable C code generation maps from Symbolics 56 | * Direct-quadrature-zero transformation for multibody systems and robotics (https://en.wikipedia.org/wiki/Direct-quadrature-zero_transformation) 57 | * Pryce's algorithm for DAE index reduction (https://link.springer.com/article/10.1023/A:1021998624799, https://inria.hal.science/hal-03104030v2/document) 58 | 59 | ### Project Type 3 Ideas: Implementation and analysis of numerical methods for acausal models 60 | 61 | * Adaptive order Radau methods (https://www.sciencedirect.com/science/article/pii/S037704279900134X) 62 | * Parallel Rosenbrock and FIRK methods 63 | * Handling the difficulties of BDFs in DAE systems (i.e. handling known deficiencies in the DFBDF algorithm) 64 | * New time stepping schema for Rosenbrock methods for DAE interpolation performance 65 | * Investigation of nonlinear solver globalization schemes for difficult DAE initialization problems 66 | 67 | ## Tentative Schedule 68 | 69 | * January 10th: Introduction to the course, Guest lecture: Brad Carman, introduction to acausal modeling for physical systems with ModelingToolkit 70 | * January 12th: Guest lecture: Brad Carman, developing high-fidelity models of hydraulic systems 71 | * January 15th: Martin Luther King Day! 72 | * January 17th: Real numerical methods for implicit equations and stiff ordinary differential equations (ODEs), i.e., Jacobian-free Newton-Krylov, adaptive time stepping, dense output, sparse automatic differentiation, event handling. 73 | * January 18th (Make up day for MLK day): Continuing discussion of stiff ODEs and onto numerical methods for differential-algebraic equations (DAEs). Rosenbrock methods, Backwards-Differentiation Formulae (BDF), fully-implicit Runge-Kutta methods. 74 | * January 19th: Finishing the discussion on stiff ODEs and DAEs. If time allows, discussion of handling inverse problems (parameter estimation), adjoint methods, uncertainty quantification, and the connections to reverse-mode AD. 75 | * January 22nd: Discussion and interactive workshop on debugging difficult stiff ODE/DAE models (featuring Brad Carman and Yingbo Ma). 76 | * January 24th: Guest Lecture: Yingbo Ma. How acausal model compilers work: index reduction. Pantelides algorithm, dummy derivatives, and demonstrations. 77 | * January 26th: Guest Lecture: Yingbo Ma. How acausal model compilers work: Tearing of nonlinear systems and alias elimination. 78 | * January 29th: Guest Lecture: Yingbo Ma. How acausal model compilers work: Loop rerolling, specialized optimizations for multibody systems, and other generated code robustness and performance optimizations. 79 | * January 31st: TBD based on what is not sufficiently covered earlier in the course. 80 | * February 2nd: Final project presentations! 81 | -------------------------------------------------------------------------------- /docs/src/img/damper.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 87 | 92 | 93 | 106 | 111 | 112 | 125 | 130 | 131 | 132 | 137 | 141 | 145 | 149 | 156 | 163 | a 174 | b 185 | f 196 | 201 | 206 | 210 | 214 | v 225 | v 236 | 237 | 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModelingToolkitCourse 2 | 3 | > [!NOTE] 4 | > This course is running live as part of MIT's IAP! For details see: 5 | > 6 | > 18.S191 Special Subject in Mathematics: Composable System Modeling and Its Compilation 7 | > 8 | > Dates: Jan 9 - Feb 2 9 | > 10 | > Time: MWF 11-12 11 | > 12 | > Location: 2-135 13 | 14 | Traditionally, modeling physical systems often requires a deep understanding of the physics and equations of motions or states, simplifying the differential equations using conservation laws and constraints, and finally implementing simplified equations in a scientific computing language to numerically solve them. However, this workflow is tedious and not expressive. A simple change in the underlying physical system often requires a complete re-derivation of the simplified equations. A composable modeling system frees domain experts from the time-consuming derivation, simplification, and implementation by allowing them to model each physical component separately and hierarchically, thereby enabling them to build more accurate and complex models without compromising the simulation performance. In this course, we will dive into the practice of implementing composable physical models and the compilation process of the model system using the ModelingToolkit.jl acausal modeling framework in Julia. Students will learn the mathematics and numerical methods behind solving industry-scale models, covering topics such as differential-algebraic equations (DAEs), modern techniques in implicit integrators (backwards differentiation formulae (BDFs)), symbolic manipulation of equations via techniques like Pantelides algorithm and tearing of nonlinear systems, and more. Applications for solving real-world problems like modeling battery systems of electric vehicles, efficient control of hydraulic and HVAC systems, and more will be used to demonstrate how these techniques are used in industrial settings. 15 | 16 | ## Syllabus 17 | 18 | Prerequisites: While this course will be mixing ideas from symbolic computing, numerical differential equations, and topics from mechanical engineering, no one in the course is expected to have covered all of these topics before. Understanding of calculus, linear algebra, and programming is essential. The course is considered self-contained starting from the basic building blocks of undergraduate differential equations. Problem sets will involve use of Julia, a Matlab-like environment (little or no prior experience required; you will learn as you go), for doing acausal modeling via the ModelingToolkit.jl system. 19 | 20 | Textbook & Other Reading: There is no textbook for this course. For a textbook that covers the practical parts of doing modeling and simulation in an acausal way, Michael Tiller's "Modelica by Example" is a good reference (see https://mbe.modelica.university/). For a textbook that covers the algorithms of acausal modeling compilers, there is no recommended textbook and lecture notes will be supplied as a primary source. 21 | 22 | Grading: The final project proposal (due January 15th) is 25%, and 75% for the final project (due February 2nd). Final projects will be submitted electronically via email. 23 | 24 | ## Final Project 25 | 26 | The final project can take two forms: 27 | 28 | 1. Developing an acausal model of some real-world system. 29 | 2. Implementation and analysis of a new acausal modeling compiler feature. 30 | 3. Implementation and analysis of numerical methods for acausal models. 31 | 32 | A final project proposal is due January 19th and the final project is due on the last day of the course. The last day will be final project presentations where the work is demonstrated to the class. 33 | 34 | The final project's deliverable can take two different forms: 35 | 36 | 1. A final project writeup: a 5-10 page paper using the style template from the [_SIAM Journal on Numerical Analysis_](http://www.siam.org/journals/auth-info.php) (or similar) explaining what was done, along with a Github repository package with the components of the model and docs/tests which demonstrate the successful composed model. 37 | 2. A pull request to one of the libraries (ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq, NonlinearSolve, etc.). For this version of the project, it is sufficient to supply a pull request to MTK/MSL with a description of the feature being implemented, tests of the transformation, and documentation showcasing its correct action on test models. 38 | 39 | We expect the work to be roughly the same for the two routes, where the 1st would entail more theory and mathematical writeup while the latter is more focused on code and documentation. Note that any project considering doing a new acausal modeling feature should heavily consider doing the pull request route as writing a toy acausal modeling compiler within the timeframe of the course is likely to be unsuccessful. 40 | 41 | Note that many of these projects are starter projects towards publications. If you're interested in continuing this work after the IAP towards a publication, please discuss during the project selection page so the project can be appropriately scoped. 42 | 43 | ### Project Type 1 Ideas: Developing an Acausal Model of A Real-World System 44 | 45 | The following sources can be used as inspiration: 46 | 47 | * Modelica "other" libraries (https://modelica.org/libraries/) 48 | * Modelica Standard Library (https://github.com/modelica/ModelicaStandardLibrary) 49 | 50 | ### Project Type 2 Ideas: Implementation and analysis of a new acausal modeling compiler feature 51 | 52 | * Automated Laplace and Fourier transforms 53 | * Automated function transformation of observables (i.e. log-transform states to enforce positivity) 54 | * Symbolic generation of sensitivity analysis equations (https://github.com/SciML/ModelingToolkit.jl/issues/39) 55 | * Lamperti transformation of stochastic differential equations (https://github.com/SciML/ModelingToolkit.jl/issues/140) 56 | * Automated conversion of distributed delay equations into ODEs (https://github.com/SciML/ModelingToolkit.jl/issues/45) 57 | * Specialized nonlinear solvers based on strongly connected components 58 | * Inline integration (https://people.inf.ethz.ch/fcellier/Pubs/OO/esm_95.pdf) 59 | * Automated detection of events from discontinuities in the ODE/DAE definition 60 | * Polynomial chaos expansions for fast uncertainty quantification 61 | * DCP on OptimizationSystem to automatically transform nonlinear optimization problems to convex optimization problems (http://cvxr.com/cvx/doc/dcp.html) 62 | * Common subexpression elimination in Symbolic code generation 63 | * Extendable C code generation maps from Symbolics 64 | * Direct-quadrature-zero transformation for multibody systems and robotics (https://en.wikipedia.org/wiki/Direct-quadrature-zero_transformation) 65 | * Pryce's algorithm for DAE index reduction (https://link.springer.com/article/10.1023/A:1021998624799, https://inria.hal.science/hal-03104030v2/document) 66 | 67 | ### Project Type 3 Ideas: Implementation and analysis of numerical methods for acausal models 68 | 69 | * Adaptive order Radau methods (https://www.sciencedirect.com/science/article/pii/S037704279900134X) 70 | * Parallel Rosenbrock and FIRK methods 71 | * Handling the difficulties of BDFs in DAE systems (i.e. handling known deficiencies in the DFBDF algorithm) 72 | * New time stepping schema for Rosenbrock methods for DAE interpolation performance 73 | * Investigation of nonlinear solver globalization schemes for difficult DAE initialization problems 74 | 75 | ## Tentative Schedule 76 | 77 | * January 10th: Introduction to the course, Guest lecture: Brad Carman, introduction to acausal modeling for physical systems with ModelingToolkit 78 | * January 12th: Guest lecture: Brad Carman, developing high-fidelity models of hydraulic systems 79 | * January 15th: Martin Luther King Day! 80 | * January 17th: Real numerical methods for implicit equations and stiff ordinary differential equations (ODEs), i.e., Jacobian-free Newton-Krylov, adaptive time stepping, dense output, sparse automatic differentiation, event handling. 81 | * January 18th (Make up day for MLK day): Continuing discussion of stiff ODEs and onto numerical methods for differential-algebraic equations (DAEs). Rosenbrock methods, Backwards-Differentiation Formulae (BDF), fully-implicit Runge-Kutta methods. 82 | * January 19th: Finishing the discussion on stiff ODEs and DAEs. If time allows, discussion of handling inverse problems (parameter estimation), adjoint methods, uncertainty quantification, and the connections to reverse-mode AD. 83 | * January 22nd: Discussion and interactive workshop on debugging difficult stiff ODE/DAE models (featuring Brad Carman and Yingbo Ma). 84 | * January 24th: Guest Lecture: Yingbo Ma. How acausal model compilers work: index reduction. Pantelides algorithm, dummy derivatives, and demonstrations. 85 | * January 26th: Guest Lecture: Yingbo Ma. How acausal model compilers work: Tearing of nonlinear systems and alias elimination. 86 | * January 29th: Guest Lecture: Yingbo Ma. How acausal model compilers work: Loop rerolling, specialized optimizations for multibody systems, and other generated code robustness and performance optimizations. 87 | * January 31st: TBD based on what is not sufficiently covered earlier in the course. 88 | * February 2nd: Final project presentations! 89 | -------------------------------------------------------------------------------- /docs/src/lectures/run_away_train_hydraulic.jl: -------------------------------------------------------------------------------- 1 | using ModelingToolkit 2 | using DifferentialEquations 3 | using ModelingToolkitStandardLibrary.Mechanical.Translational: MechanicalPort 4 | using ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible: Valve, DynamicVolume, HydraulicFluid, FixedPressure 5 | using ModelingToolkitStandardLibrary.Blocks: Constant 6 | using Plots 7 | using ModelingToolkit: t_nounits as t, D_nounits as D 8 | 9 | # NOTE: How Decouple works to provide discontinuous behavior 10 | @mtkmodel Decouple begin #Decouple(;k, d, v_a, v_b, x_a, x_b, f=0) | port_a, port_b 11 | @parameters begin 12 | k 13 | d 14 | end 15 | @variables begin 16 | v_a(t) 17 | v_b(t) 18 | x_a(t) 19 | x_b(t) 20 | f(t)=0 21 | end 22 | @components begin 23 | port_a = MechanicalPort(;v=v_a,f=+f) 24 | port_b = MechanicalPort(;v=v_b,f=-f) 25 | end 26 | @equations begin 27 | # differentials 28 | D(x_a) ~ v_a 29 | D(x_b) ~ v_b 30 | 31 | # connectors 32 | port_a.v ~ v_a 33 | port_b.v ~ v_b 34 | port_a.f ~ +f 35 | port_b.f ~ -f 36 | 37 | # physics 38 | f ~ ifelse(x_a >= x_b, (v_a - v_b)*d + k*(x_a - x_b), 0) 39 | end 40 | end 41 | 42 | @mtkmodel Mass begin #Mass(;m, v, f, a=f/m) | port 43 | @parameters begin 44 | m = 10 45 | end 46 | @variables begin 47 | v(t) 48 | f(t) = 0 49 | a(t) = f/m 50 | end 51 | @components begin 52 | port = MechanicalPort(; v, f) 53 | end 54 | @equations begin 55 | # derivatives 56 | D(v) ~ a 57 | 58 | # connectors 59 | port.v ~ v 60 | port.f ~ f 61 | 62 | # physics 63 | f ~ m*D(v) 64 | end 65 | end 66 | 67 | @mtkmodel Damper begin #Damper(;d=1,v,f) | port_a, port_b 68 | @parameters begin 69 | d = 1 70 | end 71 | @variables begin 72 | v(t) 73 | f(t) 74 | end 75 | @components begin 76 | port_a = MechanicalPort() 77 | port_b = MechanicalPort() 78 | end 79 | @equations begin 80 | # connectors 81 | (port_a.v - port_b.v) ~ v 82 | port_a.f ~ +f 83 | port_b.f ~ -f 84 | 85 | # physics 86 | f ~ d*v 87 | end 88 | end 89 | 90 | @mtkmodel Spring begin #Spring(;k=100,x,v,f) | port_a, port_b 91 | @parameters begin 92 | k = 100 93 | end 94 | @variables begin 95 | x(t) 96 | v(t) 97 | f(t) 98 | end 99 | @components begin 100 | port_a = MechanicalPort() 101 | port_b = MechanicalPort() 102 | end 103 | @equations begin 104 | # derivatives 105 | D(x) ~ v 106 | 107 | # connectors 108 | (port_a.v - port_b.v) ~ v 109 | port_a.f ~ +f 110 | port_b.f ~ -f 111 | 112 | # physics 113 | f ~ k*x 114 | end 115 | end 116 | 117 | @mtkmodel Reference begin #Reference(;f) | port 118 | @parameters begin 119 | 120 | end 121 | @variables begin 122 | f(t) 123 | end 124 | @components begin 125 | port = MechanicalPort() 126 | end 127 | @equations begin 128 | # connectors 129 | port.v ~ 0 130 | port.f ~ -f 131 | end 132 | end 133 | 134 | # NOTE: How TrainCar works to track absolute position 135 | @mtkmodel TrainCar begin #TrainCar(;m, v, x_a, x_b) | port_a, port_b 136 | @parameters begin 137 | m 138 | v 139 | end 140 | @variables begin 141 | x_a(t) 142 | x_b(t) 143 | end 144 | @components begin 145 | port_a = MechanicalPort(;v) 146 | port_b = MechanicalPort(;v) 147 | mass = Mass(;m,v) 148 | end 149 | @equations begin 150 | D(x_a) ~ port_a.v 151 | D(x_b) ~ port_b.v 152 | 153 | connect(mass.port, port_a, port_b) 154 | end 155 | end 156 | 157 | @mtkmodel Coupler begin #Coupler(;k,d,v) | port_a, port_b 158 | @parameters begin 159 | k 160 | d 161 | v 162 | end 163 | @variables begin 164 | 165 | end 166 | @components begin 167 | port_a = MechanicalPort(;v) 168 | port_b = MechanicalPort(;v) 169 | spring = Spring(;k,x=0,v=0,f=0) 170 | damper = Damper(;d,v=0,f=0) 171 | end 172 | @equations begin 173 | connect(port_a, spring.port_a, damper.port_a) 174 | connect(spring.port_b, damper.port_b, port_b) 175 | end 176 | end 177 | 178 | @mtkmodel Stopper begin #Stopper(;k,d) | port 179 | @parameters begin 180 | k 181 | d 182 | end 183 | @variables begin 184 | 185 | end 186 | @components begin 187 | port = MechanicalPort() 188 | spring = Spring(;k,x=0,v=0,f=0) 189 | damper = Damper(;d,v=0,f=0) 190 | ref = Reference() 191 | end 192 | @equations begin 193 | connect(port, spring.port_a, damper.port_a) 194 | connect(spring.port_b, damper.port_b, ref.port) 195 | end 196 | end 197 | 198 | # NOTE: How HydraulicStopper works 199 | @component function HydraulicStopper(;name, area) #HydraulicStopper(;area) | port 200 | pars = @parameters begin 201 | area = area 202 | end 203 | vars = @variables begin 204 | 205 | end 206 | systems = @named begin 207 | port = MechanicalPort() 208 | volume = DynamicVolume(5, true, false; p_int=0, area=0.1, x_int = 5, x_max = 5, x_min = 1, x_damp = 2, direction = -1) 209 | valve = Valve(;p_a_int=0, p_b_int=0, area_int=area, Cd=0.7) 210 | constarea = Constant(;k=area) 211 | open = FixedPressure(;p=0) 212 | fluid = HydraulicFluid(; density=876, bulk_modulus=1.2e9) 213 | end 214 | eqs = [ 215 | connect(port, volume.flange) 216 | connect(volume.port, valve.port_a) 217 | connect(valve.port_b, open.port) 218 | connect(constarea.output, valve.area) 219 | 220 | connect(fluid, volume.port) 221 | ] 222 | 223 | return ODESystem(eqs, t, vars, pars; systems, name) 224 | end 225 | 226 | 227 | @mtkmodel System begin 228 | @parameters begin 229 | v=10 230 | end 231 | @variables begin 232 | 233 | end 234 | @components begin 235 | car1 = TrainCar(;m=1000, v, x_a=0, x_b=0.9) 236 | cx1 = Coupler(;k=1e5,d=1e5,v) 237 | 238 | car2 = TrainCar(;m=1000, v, x_a=1.1, x_b=1.9) 239 | cx2 = Coupler(;k=1e5,d=1e5,v) 240 | 241 | car3 = TrainCar(;m=1000, v, x_a=2.1, x_b=2.9) 242 | cx3 = Coupler(;k=1e5,d=1e5,v) 243 | 244 | engine = TrainCar(;m=2000, v=10, x_a=3.1, x_b=3.9) 245 | decouple = Decouple(;k=1e6, d=1e6, v_a=v, v_b=0, x_a=3.9, x_b=10, f=0) 246 | stopper = HydraulicStopper(; area=1e-2) 247 | end 248 | @equations begin 249 | connect(car1.port_b, cx1.port_a) 250 | connect(car2.port_a, cx1.port_b) 251 | connect(car2.port_b, cx2.port_a) 252 | connect(car3.port_a, cx2.port_b) 253 | connect(car3.port_b, cx3.port_a) 254 | connect(engine.port_a, cx3.port_b) 255 | connect(engine.port_b, decouple.port_a) 256 | connect(decouple.port_b, stopper.port) 257 | end 258 | end 259 | 260 | @named odesys = System() 261 | using JuliaSimCompiler 262 | sys = structural_simplify(IRSystem(odesys)) 263 | @mtkbuild sys = System() 264 | 265 | # NOTE: missing_variable_defaults() 266 | prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0, 5)) 267 | prob = ODEProblem(sys, [], (0, 5)) 268 | # NOTE: strategies for challenging solve 269 | @time "solve Rodas" solve(prob, Rodas4()); 270 | @time "solve" sol = solve(prob, ImplicitEuler(nlsolve=NLNewton(check_div=false, always_new=true, relax=4/10, max_iter=100)); adaptive=false, dt=1e-5); 271 | 272 | 273 | 274 | # NOTE: pressure wave and no negative pressure 275 | plot(sol; 276 | idxs=[sys.stopper.volume.v1.port.p/1e5, 277 | sys.stopper.volume.v2.port.p/1e5, 278 | sys.stopper.volume.v3.port.p/1e5, 279 | sys.stopper.volume.v4.port.p/1e5, 280 | sys.stopper.volume.v5.port.p/1e5], 281 | xlims=(0.72, 0.74), 282 | ylabel="pressure [bar]", 283 | xlabel="time [s]") 284 | 285 | # NOTE: pressure bump at the end when the valve closes (1m x_min) 286 | plot(sol; 287 | idxs=[sys.stopper.volume.v1.port.p/1e5, 288 | sys.stopper.volume.v2.port.p/1e5, 289 | sys.stopper.volume.v3.port.p/1e5, 290 | sys.stopper.volume.v4.port.p/1e5, 291 | sys.stopper.volume.v5.port.p/1e5], 292 | ylims=(0, 20), 293 | ylabel="pressure [bar]", 294 | xlabel="time [s]") 295 | 296 | plot(sol; 297 | idxs=[sys.engine.mass.v]) 298 | 299 | # NOTE: How to design safe acceleration limit? 300 | g = 9.807 301 | plot(sol; 302 | idxs=[ 303 | sys.car1.mass.a/g, 304 | sys.car2.mass.a/g, 305 | sys.car3.mass.a/g, 306 | sys.engine.mass.a/g 307 | ], 308 | ylims=(-25,5), 309 | xlims=(0.7,0.85), 310 | ylabel="acceleration [g]", 311 | xlabel="time [s]") 312 | -------------------------------------------------------------------------------- /docs/src/img/Example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 30 | 35 | 36 | 43 | 49 | 50 | 57 | 63 | 64 | 71 | 77 | 78 | 85 | 91 | 92 | 93 | 114 | 123 | 124 | 126 | 127 | 129 | image/svg+xml 130 | 132 | 133 | 134 | 135 | 140 | 153 | 159 | 165 | 171 | 177 | 183 | 189 | 195 | g 206 | 212 | x 223 | M 234 | p 245 | 250 | A 261 | 262 | 263 | -------------------------------------------------------------------------------- /docs/src/lectures/lecture1.jl: -------------------------------------------------------------------------------- 1 | using ForwardDiff 2 | using Plots 3 | 4 | d=1 5 | k=1000 6 | Δt=1e-3 7 | F = 100 8 | 9 | function f(xᵢ, xᵢ₋₁) 10 | 11 | ẋᵢ = (xᵢ - xᵢ₋₁)/Δt 12 | lhs = d*ẋᵢ + k*xᵢ^1.5 13 | rhs = F 14 | 15 | return lhs - rhs 16 | end 17 | 18 | # Newton's Method 19 | # first time step (i=2) 20 | xᵢ₋₁ = 0.0 21 | xᵢ = xᵢ₋₁ #<-- guess 22 | g(xᵢ) = f(xᵢ, xᵢ₋₁) 23 | xᵢ -= g(xᵢ)/ForwardDiff.derivative(g, xᵢ) 24 | xᵢ -= g(xᵢ)/ForwardDiff.derivative(g, xᵢ) 25 | xᵢ -= g(xᵢ)/ForwardDiff.derivative(g, xᵢ) 26 | 27 | 28 | # --------------------------------- 29 | 30 | tol = 1e-3 31 | x = zeros(10) 32 | for i=2:10 33 | g(xᵢ) = f(xᵢ, x[i-1]) 34 | Δx = Inf 35 | while abs(Δx) > tol 36 | Δx = g(x[i])/ForwardDiff.derivative(g, x[i]) 37 | x[i] -= Δx 38 | end 39 | end 40 | 41 | plot(x; ylabel="x [m]", xlabel="time step") 42 | 43 | # --------------------------------- 44 | using DifferentialEquations 45 | 46 | prob = NonlinearProblem(f, 0.0, 0.0) 47 | sol=solve(prob, NewtonRaphson(); abstol=tol) 48 | 49 | # --------------------------------- 50 | 51 | x = zeros(10) 52 | for i=2:10 53 | prob′ = remake(prob; u0=x[i], p=x[i-1]) 54 | sol = solve(prob′, NewtonRaphson(); abstol=tol) 55 | x[i] = sol[] 56 | end 57 | plot(x; ylabel="x [m]", xlabel="time step") 58 | 59 | # --------------------------------- 60 | 61 | function du_dt(u,p,t) 62 | F, k, d = p 63 | x = u 64 | return (F - k*x^1.5)/d 65 | end 66 | 67 | prob = ODEProblem(du_dt, 0.0, (0.0, 0.01), [F, k, d]) 68 | sol = solve(prob) 69 | plot(sol; xlabel="time [s]", ylabel="x [m]") 70 | 71 | # --------------------------------- 72 | tol = 1e-3 73 | d=1 74 | k=1000 75 | F = 100 76 | 77 | function du_dt1(u,p,t) 78 | F, k, d = p 79 | x, ẋ = u 80 | 81 | eqs = [ 82 | ẋ # D(x) = ẋ 83 | (d*ẋ + k*x^1.5) - (F) # 0 = ( lhs ) - ( rhs ) 84 | ] 85 | 86 | return eqs 87 | end 88 | 89 | fmm = ODEFunction(du_dt1; mass_matrix=[1 0;0 0]) 90 | prob = ODEProblem(fmm, [0.0, F/d], (0.0, 0.01), [F, k, d]) 91 | sol = solve(prob; abstol=tol) 92 | plot(sol; layout=2) 93 | 94 | 95 | function du_dt1(du,u,p,t) 96 | F, k, d = p 97 | x, ẋ, ẍ = u 98 | 99 | du[1] = ẋ 100 | du[2] = ẍ 101 | du[3] = (d*ẋ + k*(x^1.5)) - (F) 102 | 103 | end 104 | 105 | fmm = ODEFunction(du_dt1; mass_matrix=[1 0 0;0 1 0;0 0 0]) 106 | prob = ODEProblem(fmm, [0.0, F/d, 0.0], (0.0, 0.01), [F, k, d]) 107 | 108 | sol = solve(prob, ImplicitEuler(autodiff=false); abstol=tol) 109 | sol = solve(prob, ImplicitEuler(); abstol=tol, dt=0.001, adaptive=false) 110 | sol = solve(prob, Rosenbrock23(); abstol=tol) 111 | 112 | plot(sol; layout=3) 113 | 114 | using ModelingToolkitComponents: SimpleImplicitEuler 115 | sol = solve(prob, SimpleImplicitEuler(); abstol=tol) 116 | plot(sol; layout=3) 117 | 118 | function du_dt2(u,p,t) 119 | F, k, d = p 120 | x, ẋ, ẍ = u 121 | 122 | eqs = [ 123 | ẋ # D(x) = ẋ 124 | ẍ # D(ẋ) = ẍ 125 | (d*ẍ + 1.5*k*(x^0.5)*ẋ) - (0) # 0 = ( lhs ) - ( rhs ) 126 | ] 127 | 128 | return eqs 129 | end 130 | 131 | fmm = ODEFunction(du_dt2; mass_matrix=[1 0 0;0 1 0;0 0 0]) 132 | prob = ODEProblem(fmm, [0.0, F/d, 0.0], (0.0, 0.01), [F, k, d]) 133 | sol = solve(prob, ImplicitEuler(); abstol=tol) 134 | 135 | 136 | 137 | sol′ = solve(prob, RadauIIA5(); abstol=tol, reltol=10.0) 138 | 139 | plot(sol′ ; layout=3) 140 | 141 | plot(sol; idxs=1, xlabel="time [s]", ylabel="x [m]") 142 | plot!(sol′; idxs=1, xlabel="time [s]", ylabel="x [m]") 143 | 144 | plot(sol; idxs=2, xlabel="time [s]", ylabel="ẋ [m/s]") 145 | plot!(sol′; idxs=2, xlabel="time [s]", ylabel="ẋ [m/s]") 146 | 147 | plot(0:1e-5:0.01, x->ForwardDiff.derivative(t->sol(t), x)[2]; xlabel="time [s]", ylabel="ẍ [m/s^2]") 148 | plot!(sol′; idxs=3, xlabel="time [s]", ylabel="ẍ [m/s^2]") 149 | # --------------------------------- 150 | 151 | using ModelingToolkit 152 | using ModelingToolkit: t_nounits as t, D_nounits as D 153 | 154 | vars = @variables x(t)=0.0 ẋ(t)=F/d ẍ(t)=0.0 155 | eqs = [ 156 | D(x) ~ ẋ 157 | D(ẋ) ~ ẍ 158 | d*ẋ + k*x^1.5 ~ F 159 | ] 160 | @mtkbuild odesys = ODESystem(eqs, t, vars, []) 161 | sys = structural_simplify(odesys) 162 | prob = ODEProblem(sys, [], (0.0, 0.01)) 163 | sol = solve(prob; abstol=tol) 164 | plot(sol; idxs=ẍ, xlabel="time [s]", ylabel="ẍ [m/s^2]") 165 | 166 | ẍ_sol = sol[ẍ] 167 | t_sol = sol.t 168 | 169 | 170 | # --------------------------------- 171 | @connector MechanicalPort begin 172 | v(t) 173 | f(t), [connect = Flow] 174 | end 175 | 176 | @mtkmodel Mass begin 177 | @parameters begin 178 | m = 10 179 | end 180 | @variables begin 181 | v(t) = 0 182 | f(t) = 0 183 | end 184 | @components begin 185 | port = MechanicalPort(;v=v, f=f) 186 | end 187 | @equations begin 188 | # connectors 189 | port.v ~ v 190 | port.f ~ f 191 | 192 | # physics 193 | f ~ m*D(v) 194 | end 195 | end 196 | 197 | @mtkmodel Damper begin 198 | @parameters begin 199 | d = 1 200 | end 201 | @variables begin 202 | v(t) = 0 203 | f(t) = d*v 204 | end 205 | @components begin 206 | port = MechanicalPort(;v=v, f=f) 207 | end 208 | @equations begin 209 | # connectors 210 | port.v ~ v 211 | port.f ~ f 212 | 213 | # physics 214 | f ~ d*v 215 | end 216 | end 217 | 218 | @mtkmodel System begin 219 | @parameters begin 220 | v 221 | m 222 | d 223 | end 224 | @components begin 225 | mass = Mass(;v,m) 226 | damper = Damper(;v,d) 227 | end 228 | @equations begin 229 | connect(mass.port, damper.port) 230 | end 231 | end 232 | 233 | @mtkbuild sys = System(;v=100, m=5, d=3) 234 | full_equations(sys) 235 | 236 | defs = ModelingToolkit.defaults(sys) 237 | defs[sys.damper.d] 238 | 239 | 240 | 241 | 242 | 243 | prob1 = ODEProblem(sys, [], (0,10)) 244 | sol1 = solve(prob1) 245 | prob2 = remake(prob1; p=[sys.m=>3, sys.d=>4]) 246 | sol2 = solve(prob2) 247 | plot(sol1; idxs=sys.mass.v) 248 | plot!(sol2; idxs=sys.mass.v) 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | using DAE2AE 271 | aesys = dae_to_ae(odesys, Δt, 2) 272 | sys = no_simplify(aesys) 273 | prob = ODEProblem(sys, [], (0.0, 0.01)) 274 | sol = solve(prob, ImplicitEuler(nlsolve=NLNewton(check_div=false)); abstol=tol, dt=Δt, adaptive=false, initializealg=NoInit()) 275 | plot(sol; idxs=ẍ, xlabel="time [s]", ylabel="ẍ [m/s^2]") 276 | 277 | ẍ_sol = sol[ẍ] 278 | t_sol = sol.t 279 | 280 | 281 | 282 | 283 | # ------------------- 284 | # ------------------- 285 | # ------------------- 286 | # --- PROJECT ----- 287 | # ------------------- 288 | # ------------------- 289 | # ------------------- 290 | 291 | Δt = 1e-3 292 | 293 | 294 | function f(Xᵢ, P) 295 | 296 | Xᵢ₋₁, Xᵢ₋₂ = P 297 | 298 | xᵢ, ẋᵢ, ẍᵢ = Xᵢ 299 | xᵢ₋₁, ẋᵢ₋₁, ẍᵢ₋₁ = Xᵢ₋₁ 300 | xᵢ₋₂, ẋᵢ₋₂, ẍᵢ₋₂ = Xᵢ₋₂ 301 | 302 | eqs = [ 303 | ( ẋᵢ ) - ( (3*xᵢ - 4*xᵢ₋₁ + xᵢ₋₂)/(2*Δt) ) 304 | ( ẍᵢ ) - ( (3*ẋᵢ - 4*ẋᵢ₋₁ + ẋᵢ₋₂)/(2*Δt) ) 305 | ( d*ẋᵢ + k*xᵢ^1.5 ) - ( F ) 306 | ] 307 | 308 | return eqs 309 | end 310 | 311 | T = (0:Δt:Δt*99) 312 | X = zeros(100,3) 313 | X[1,2] = F/d 314 | 315 | prob = NonlinearProblem(f, X[1,:], (X[1,:],X[1,:])) 316 | sol = solve(prob, NewtonRaphson()) 317 | 318 | for i=2:100 319 | prob′ = remake(prob; u0=X[i-1,:], p=(X[i-1,:],X[i>2 ? i-2 : i-1, :])) 320 | sol = solve(prob′, NewtonRaphson(); abstol=tol) 321 | X[i,:] = sol[:] 322 | end 323 | plot(X[:,1]; ylabel="x [m]", xlabel="time step") 324 | plot(X[:,2]; ylabel="ẋ [m/s]", xlabel="time step") 325 | 326 | plot(T, X[:,3]; ylabel="ẍ [m/s²]", xlabel="time [s]") 327 | plot!(t_sol, ẍ_sol) 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | xo = zeros(11) 352 | ẋo = zeros(11) 353 | ẋo[1] = F/d 354 | function du_dt3(u,p,t) 355 | F, k, d, Δt = p 356 | x, ẋ, ẍ = u 357 | 358 | i = round(Int, t/Δt)+1 359 | xo[i] = ForwardDiff.value(x) 360 | ẋo[i] = ForwardDiff.value(ẋ) 361 | 362 | eqs = [ 363 | (ẋ) - (x-xo[i>1 ? i-1 : i])/Δt # D(x) = ẋ 364 | (ẍ) - (ẋ-ẋo[i>1 ? i-1 : i])/Δt # D(ẋ) = ẍ 365 | (d*ẋ + k*x^1.5) - (F) # 0 = ( lhs ) - ( rhs ) 366 | ] 367 | 368 | 369 | return eqs 370 | end 371 | 372 | fmm = ODEFunction(du_dt3; mass_matrix=[0 0 0;0 0 0;0 0 0]) 373 | prob = ODEProblem(fmm, [0.0, F/d, 0.0], (0.0, 0.01), [F, k, d, Δt]) 374 | sol′′ = solve(prob, ImplicitEuler(nlsolve=NLNewton(always_new=true, check_div=false, relax=4//10)); abstol=tol, adaptive=false, dt=Δt, initializealg=NoInit()) 375 | # 376 | plot(sol′′ ; idxs=3, xlabel="time [s]", ylabel="ẍ [m/s^2]") 377 | 378 | 379 | 380 | fe(x,p,t) = error_tracker(f, x, p, t) 381 | 382 | function step_julia(xₒ, t) 383 | 384 | x = [xₒ] 385 | p = [Δt, d, k, xₒ] 386 | t += Δt 387 | g(x) = fe(x,p,t) 388 | 389 | while norm(g(x)) > tol 390 | x = x .- ForwardDiff.jacobian(g, x)\g(x) 391 | end 392 | 393 | return x[1], t 394 | end 395 | 396 | function solver_julia() 397 | xs = zeros(10) 398 | t = 0.0 399 | for i=2:10 400 | xs[i], t = step_julia(xs[i-1], t) 401 | end 402 | return xs 403 | end 404 | 405 | @time x_julia=solver_julia(); 406 | errors_julia = copy(errors) 407 | 408 | plot(x_julia) 409 | 410 | plot(iteration_error(errors_julia); yscale=:log10) 411 | hline!([tol]) 412 | 413 | plot(iteration_number(errors_julia)) 414 | 415 | 416 | 417 | 418 | # DifferentialEquations.jl 419 | using DifferentialEquations 420 | 421 | xₒ=0.0 422 | p = [Δt, d, k, xₒ] 423 | prob = SteadyStateProblem(fe, [xₒ], p) 424 | sol = solve(prob, NewtonRaphson(); abstol=tol) 425 | 426 | function step_diffeq(xₒ, t) 427 | 428 | prob′ = remake(prob, p=[Δt, d, k, xₒ], u0=[xₒ]) 429 | sol = solve(prob′, NewtonRaphson(); abstol=tol) 430 | 431 | for (i,er) in enumerate(errors) 432 | if isinf(er[1]) 433 | errors[i] = (t, er[2], er[3]) 434 | end 435 | end 436 | 437 | return sol.u[] 438 | end 439 | 440 | empty!(errors) 441 | function solver_diffeq() 442 | xs = zeros(10) 443 | t = 0.0 444 | for i=2:10 445 | t += Δt 446 | xs[i] = step_diffeq(xs[i-1], t) 447 | end 448 | return xs 449 | end 450 | 451 | @time x_diffeq=solver_diffeq(); 452 | plot(x_diffeq) 453 | plot!(x_julia) 454 | 455 | errors_diffeq = copy(errors) 456 | 457 | plot(iteration_error(errors_julia); yscale=:log10) 458 | plot!(iteration_error(errors_diffeq); yscale=:log10) 459 | hline!([tol]) 460 | 461 | plot(iteration_number(errors_julia)) 462 | plot!(iteration_number(errors_diffeq)) 463 | 464 | fode = ODEFunction(fe, mass_matrix=zeros(1,1)) 465 | prob = ODEProblem(fode, [0.0], (0, Inf), [Δt, d, k, xₒ]) 466 | empty!(errors) 467 | integrator = init(prob, ImplicitEuler(nlsolve=NLNewton(always_new=false, max_iter=100)); adaptive=false, dt=Δt, abstol=tol, initializealg=NoInit()) 468 | 469 | function solver_ie() 470 | xs = zeros(10) 471 | 472 | for i=2:10 473 | integrator.p[end] = xs[i-1] 474 | step!(integrator) 475 | xs[i] = integrator.u[1] 476 | end 477 | return xs 478 | end 479 | 480 | x_ie = solver_ie() 481 | 482 | errors_ie = copy(errors) 483 | 484 | plot(x_diffeq) 485 | plot!(x_julia) 486 | plot!(x_ie) 487 | 488 | plot(iteration_error(errors_julia); yscale=:log10) 489 | plot!(iteration_error(errors_diffeq); yscale=:log10) 490 | plot!(iteration_error(errors_ie); yscale=:log10) 491 | hline!([tol]) 492 | 493 | plot(iteration_number(errors_julia)) 494 | plot!(iteration_number(errors_diffeq)) 495 | plot!(iteration_number(errors_ie)) 496 | 497 | 498 | 499 | using ModelingToolkit 500 | using DAE2AE 501 | using ModelingToolkit: t_nounits as t, D_nounits as D 502 | 503 | vars = @variables x(t)=0 504 | 505 | eqs =[ 506 | D(x) ~ (F - k*x^1.5)/d 507 | ] 508 | 509 | @mtkbuild odesys = ODESystem(eqs, t, vars, []) 510 | 511 | aesys = DAE2AE.dae_to_ae(odesys, Δt) 512 | sys = structural_simplify(aesys) 513 | prob = ODEProblem(sys,[], (0, Inf), []) 514 | integrator = init(prob, ImplicitEuler(); adaptive=false, dt=Δt, abstol=tol, initializealg=NoInit()) 515 | step!(integrator) -------------------------------------------------------------------------------- /docs/src/img/momentum_balance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 48 | 49 | 51 | 64 | 69 | 70 | 83 | 88 | 89 | 102 | 107 | 108 | 121 | 126 | 127 | 140 | 145 | 146 | 147 | 152 | 159 | 163 | 167 | 171 | p1 A 184 | 188 | p2 A 201 | 205 | Fviscous 218 | 222 | Fviscous 235 | 239 | ρ V du/dt 250 | cv 261 | 266 | 271 | x1 284 | x2 297 | 303 | 309 | 310 | 311 | -------------------------------------------------------------------------------- /docs/src/img/ports.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 52 | 53 | 55 | 68 | 73 | 74 | 87 | 92 | 93 | 106 | 111 | 112 | 125 | 130 | 131 | 144 | 149 | 150 | 151 | 156 | 160 | 164 | 168 | 175 | 182 | a 193 | b 204 | f 215 | 220 | 225 | 229 | 233 | 237 | 241 | v 252 | f 263 | v 274 | f 285 | 292 | 296 | 300 | 304 | v 315 | f 326 | 330 | v 341 | 345 | v 356 | 360 | v 371 | 376 | f 387 | 388 | 389 | -------------------------------------------------------------------------------- /docs/src/lectures/lecture2.md: -------------------------------------------------------------------------------- 1 | # Developing high-fidelity models of hydraulic systems 2 | Why focus on hydraulics? The answer is essentially hydraulic modelling is really hard (in numerical computing terms, hydraulic models are often referred to as "stiff" ODE's, which require more rigorous solvers from standard ODE's). Solving the challenges of modeling hydraulics is applicable to the numerical modeling challenges of all other domains. Let's first start with the concept of *compressibility*. Often we think of a liquid as incompressible, imagine attempting to "squeeze" water, it can be done but takes some very high forces. Therefore, if the model in question won't be solving a problem with high forces, it can be assumed incompressible. However, most hydrulic industrial models will involve high forces, this is precisely the area where most hydraulic machines are used. 3 | 4 | ## Compressibility 5 | 6 | ### Density 7 | 8 | Density is simply mass over volume 9 | 10 | ```math 11 | \rho = m/V 12 | ``` 13 | 14 | Given a volume and mass of liquid, if the volume were to change from ``V_0`` to ``V``, we know that the pressure would increase, and since the mass in this case was constant, the density will increase as well. 15 | 16 | ![volume change](../img/VolumeChange.svg) 17 | 18 | The change in pressure for an isothermal compressible process is typically given as 19 | 20 | ```math 21 | \Delta p = -\beta \frac{\Delta V}{V_0} 22 | ``` 23 | 24 | ### Calculating Density as a Function of Pressure 25 | 26 | Substituting ``\Delta p`` and ``\Delta V`` 27 | 28 | ```math 29 | p - p_0 = -\beta \frac{V - V_0}{V_0} 30 | ``` 31 | 32 | substituting ``V = m / \rho `` 33 | 34 | ```math 35 | p - p_0 = -\beta (1 - \rho/\rho_0) 36 | ``` 37 | 38 | Solving for ``\rho`` 39 | 40 | ```math 41 | \rho = \rho_0 (1 + (p - p_0)/\beta) 42 | ``` 43 | 44 | Taking a known ``\rho_0`` when ``p_0`` is 0 (at gage pressure), simplifies to 45 | 46 | ```math 47 | \rho = \rho_0 (1 + p/\beta) 48 | ``` 49 | 50 | ### Change in Mass 51 | 52 | Conservation of mass gives us 53 | 54 | ```math 55 | m_{in} - m_{out} = m_s 56 | ``` 57 | 58 | The stored mass of oil is simply 59 | 60 | ```math 61 | m_s = \rho V 62 | ``` 63 | 64 | Taking the derivative gives us the rate of mass change 65 | 66 | ```math 67 | \dot{m}_{in} - \dot{m}_{out} = \frac{\delta (\rho V)}{\delta t} 68 | ``` 69 | 70 | Here is where the standard hydraulic modeling often makes a simplification. 71 | 72 | Correct Derivation (1): 73 | 74 | ```math 75 | \frac{\delta (\rho V)}{\delta t} = \dot{\rho} V + \rho \dot{V} 76 | ``` 77 | 78 | Standard Practice[^1] (2): 79 | 80 | ```math 81 | \color{red} \frac{\delta (\rho V)}{\delta t} = \dot{\rho} V + \rho_0 \dot{V} 82 | ``` 83 | 84 | Given ``\dot{\rho} = \rho_0 (\dot{p} / \beta)``, and ``q = \dot{m}/\rho_0`` the above is often written as 85 | 86 | ```math 87 | \color{red} q_{in} - q_{out} = (\dot{p} / \beta) V + \dot{V} 88 | ``` 89 | 90 | [^1]: See [simscape hydraulic chamber](https://www.mathworks.com/help/simscape/ref/variablehydraulicchamber.html). Note the deprecation warning moving to isothermal liquid library which uses the correct derivation. 91 | 92 | ### Example 93 | Problem Definition - Given: 94 | 95 | - ``M = 10,000 kg`` 96 | - ``A = 0.01 m^2`` 97 | - ``\rho_0 = 876 kg/m^3`` 98 | - ``\beta = 1.2e9 Pa`` 99 | - ``g = 9.807 m/s^2`` 100 | 101 | ![example](../img/Example.svg) 102 | 103 | Find the mass flow rate (``\dot{m}``) that provides a sinusodial output of ``x``: 104 | 105 | ```math 106 | x(t) = amp \cdot sin(2πtf) + x_0 107 | ``` 108 | 109 | There are 3 fundamental equations needed to solve this problem. 110 | 111 | **(1) Mass balance**: 112 | 113 | ```math 114 | \dot{m} = \dot{\rho} \cdot V + \rho \cdot \dot{V} 115 | ``` 116 | 117 | where ``V`` is the cylinder volume ``=x \cdot A`` 118 | 119 | **(2) Newton's law**: 120 | 121 | ```math 122 | M \cdot \ddot{x} = p \cdot A - m \cdot g 123 | ``` 124 | 125 | **(3) Density equation**: 126 | 127 | ```math 128 | \rho = \rho_0 (1 + p/\beta) 129 | ``` 130 | 131 | The variables of this system are ``x``, ``p``, ``\rho``, and ``\dot{m}``. By including 1 input condition that gives 4 equations and 4 variables to be solved. We will solve the problem 3 different ways 132 | 133 | ![cases](../img/cases.svg) 134 | 135 | - case 1: guess $\dot{m}$, partial mass balance 136 | - case 2: guess $\dot{m}$, complete mass balance 137 | - case 3: solution, solve $\dot{m}$ directly 138 | 139 | !!! note "mass flow guess" 140 | 141 | We know that mass flow rate thru a pipe is equal to 142 | 143 | ```math 144 | \dot{m} = \rho \bar{u} A 145 | ``` 146 | 147 | where ``\bar{u}`` is the average flow velocity thru cross section ``A``. We can assume that ``\bar{u} \approx \dot{x}``. Therefore we have 148 | 149 | ```math 150 | \dot{m} = \rho \cdot \dot{x} \cdot A 151 | ``` 152 | 153 | To solve this in ModelingToolkit.jl, let's start by defining our parameters and `x` function 154 | 155 | ```@example l2 156 | using ModelingToolkit 157 | using DifferentialEquations 158 | using Symbolics 159 | using Plots 160 | using ModelingToolkit: t_nounits as t, D_nounits as D 161 | 162 | # parameters ------- 163 | pars = @parameters begin 164 | r₀ = 876 #kg/m^3 165 | β = 1.2e9 #Pa 166 | A = 0.01 #m² 167 | x₀ = 1.0 #m 168 | M = 10_000 #kg 169 | g = 9.807 #m/s² 170 | amp = 5e-2 #m 171 | f = 15 #Hz 172 | end 173 | 174 | dt = 1e-4 #s 175 | t_end = 0.2 #s 176 | time = 0:dt:t_end 177 | 178 | x_fun(t,amp,f) = amp*sin(2π*t*f) + x₀ 179 | nothing # hide 180 | ``` 181 | 182 | Now, to supply ``\dot{m}`` we need an ``\dot{x}`` function. This can be automatically generated for us with Symbolics.jl 183 | 184 | ```@example l2 185 | ẋ_fun = build_function(expand_derivatives(D(x_fun(t,amp,f))), t, amp, f; expression=false) 186 | ``` 187 | 188 | As can be seen, we get a `cos` function as expected taking the derivative of `sin`. Now let's build the variables and equations of our system. The base equations are generated in a function so we can easily compare the correct derivation of mass balance (`density_type = r(t)`) with the standard practice (`density_type = r₀`). 189 | 190 | ```@example l2 191 | vars = @variables begin 192 | x(t) = x₀ 193 | ẋ(t) 194 | ẍ(t) 195 | p(t) = M*g/A #Pa 196 | ṁ(t) 197 | r(t) 198 | ṙ(t) 199 | end 200 | 201 | function get_base_equations(density_type) 202 | 203 | eqs = [ 204 | D(x) ~ ẋ 205 | D(ẋ) ~ ẍ 206 | D(r) ~ ṙ 207 | 208 | r ~ r₀*(1 + p/β) 209 | 210 | ṁ ~ ṙ*x*A + (density_type)*ẋ*A 211 | M*ẍ ~ p*A - M*g 212 | ] 213 | 214 | return eqs 215 | end 216 | nothing # hide 217 | ``` 218 | 219 | Note: we've only specified the initial values for the known states of `x` and `p`. We will find the additional unknown initial conditions before solving. Now we have 7 variables defined and only 6 equations, missing the final driving input equation. Let's build 3 different cases: 220 | 221 | **case 1**: 222 | 223 | ```@example l2 224 | eqs_ṁ1 = [ 225 | get_base_equations(r₀)... 226 | ṁ ~ ẋ_fun(t,amp,f)*A*r # (4) Input - mass flow guess 227 | ] 228 | nothing # hide 229 | ``` 230 | 231 | **case 2**: 232 | 233 | ```@example l2 234 | eqs_ṁ2 = [ 235 | get_base_equations(r)... 236 | ṁ ~ ẋ_fun(t,amp,f)*A*r # (4) Input - mass flow guess 237 | ] 238 | nothing # hide 239 | ``` 240 | 241 | **case 3**: 242 | 243 | ```@example l2 244 | eqs_x = [ 245 | get_base_equations(r)... 246 | x ~ x_fun(t,amp,f) # (4) Input - target x 247 | ] 248 | nothing # hide 249 | ``` 250 | 251 | Now we have 3 sets of equations, let's construct the systems and solve. If we start with case 3 with the target ``x`` input, notice that the `structural_simplify` step outputs a system with 0 equations! 252 | 253 | ```@example l2 254 | @mtkbuild odesys_x = ODESystem(eqs_x, t, vars, pars) 255 | nothing # hide 256 | ``` 257 | 258 | ```@repl l2 259 | odesys_x 260 | ``` 261 | 262 | What this means is ModelingToolkit.jl has found that this model can be solved entirely analytically. The full system of equations has been moved to what is called "observables", which can be obtained using the `observed()` function 263 | 264 | ```@repl l2 265 | observed(odesys_x) 266 | ``` 267 | 268 | !!! note "dummy derivatives" 269 | Some of the observables have a `ˍt` appended to the name. These are called dummy derivatives, which are a consequence of the algorithm to reduce the system DAE index. 270 | 271 | This system can still be "solved" using the same steps to generate an `ODESolution` which allows us to easily obtain any calculated observed state. 272 | 273 | ```@example l2 274 | prob_x = ODEProblem(odesys_x, [], (0, t_end)) 275 | sol_x = solve(prob_x; saveat=time) 276 | plot(sol_x; idxs=ṁ) 277 | ``` 278 | 279 | Now let's solve the other system and compare the results. 280 | 281 | ```@example l2 282 | @mtkbuild odesys_ṁ1 = ODESystem(eqs_ṁ1, t, vars, pars) 283 | nothing # hide 284 | ``` 285 | 286 | ```@repl l2 287 | odesys_ṁ1 288 | ``` 289 | 290 | Notice that now, with a simple change of the system input variable, `structural_simplify()` outputs a system with 4 states to be solved. We can find the initial conditions needed for these states from `sol_x` and solve. 291 | 292 | ```@example l2 293 | u0 = [sol_x[s][1] for s in unknowns(odesys_ṁ1)] 294 | prob_ṁ1 = ODEProblem(odesys_ṁ1, u0, (0, t_end)) 295 | @time sol_ṁ1 = solve(prob_ṁ1; initializealg=NoInit()); 296 | nothing # hide 297 | ``` 298 | 299 | The resulting mass flow rate required to hit the target ``x`` position can be seen to be completely wrong. This is the large impact that compressibility can have when high forces are involved. 300 | 301 | ```@example l2 302 | plot(sol_ṁ1; idxs=ṁ, label="guess", ylabel="ṁ") 303 | plot!(sol_x; idxs=ṁ, label="solution") 304 | ``` 305 | 306 | If we now solve for case 2, we can study the impact the compressibility derivation 307 | 308 | ```@example l2 309 | @mtkbuild odesys_ṁ2 = ODESystem(eqs_ṁ2, t, vars, pars) 310 | prob_ṁ2 = ODEProblem(odesys_ṁ2, u0, (0, t_end)) 311 | @time sol_ṁ2 = solve(prob_ṁ2; initializealg=NoInit()); 312 | nothing # hide 313 | ``` 314 | 315 | As can be seen, a significant error forms between the 2 cases. Plotting first the absolute position. 316 | 317 | ```@example l2 318 | plot(sol_x; idxs=x, label="solution", ylabel="x") 319 | plot!(sol_ṁ1; idxs=x, label="case 1: r₀") 320 | plot!(sol_ṁ2; idxs=x, label="case 2: r") 321 | ``` 322 | 323 | And now plotting the difference between case 1 and 2. 324 | 325 | ```@example l2 326 | plot(time, (sol_ṁ1(time)[x] .- sol_ṁ2(time)[x])/1e-3, 327 | label="x", 328 | ylabel="error (case 1 - case 2) [mm]", 329 | xlabel="t [s]" 330 | ) 331 | ``` 332 | 333 | Also note the difference in computation. 334 | 335 | ```@repl l2 336 | sol_ṁ1.destats 337 | ``` 338 | 339 | As can be seen, including the detail of full compressibility resulted in more computation: more function evaluations, Jacobians, solves, and steps. 340 | 341 | ```@repl l2 342 | sol_ṁ2.destats 343 | ``` 344 | 345 | ### ModelingToolkitStandardLibrary.jl 346 | Now let's re-create this example using components from the ModelingToolkitStandardLibrary.jl. It can be shown that by connecting `Mass` and `Volume` components that the same exact result is achieved. The important thing is to pay very close attention to the initial conditions. 347 | 348 | ```@example l2 349 | import ModelingToolkitStandardLibrary.Mechanical.Translational as T 350 | import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC 351 | import ModelingToolkitStandardLibrary.Blocks as B 352 | 353 | using DataInterpolations 354 | mass_flow_fun = LinearInterpolation(sol_x[ṁ], sol_x.t) 355 | 356 | function MassVolume(; name, dx, drho, dm) 357 | 358 | pars = @parameters begin 359 | A = 0.01 #m² 360 | x₀ = 1.0 #m 361 | M = 10_000 #kg 362 | g = 9.807 #m/s² 363 | amp = 5e-2 #m 364 | f = 15 #Hz 365 | p_int=M*g/A 366 | dx=dx 367 | drho=drho 368 | dm=dm 369 | end 370 | vars = [] 371 | systems = @named begin 372 | fluid = IC.HydraulicFluid(; density = 876, bulk_modulus = 1.2e9) 373 | mass = T.Mass(;v=dx,m=M,g=-g) 374 | vol = IC.Volume(;area=A, x=x₀, p=p_int, dx, drho, dm) 375 | mass_flow = IC.MassFlow(;p_int) 376 | mass_flow_input = B.TimeVaryingFunction(;f = mass_flow_fun) 377 | end 378 | 379 | eqs = [ 380 | connect(mass.flange, vol.flange) 381 | connect(vol.port, mass_flow.port) 382 | connect(mass_flow.dm, mass_flow_input.output) 383 | connect(mass_flow.port, fluid) 384 | ] 385 | 386 | return ODESystem(eqs, t, vars, pars; systems, name) 387 | end 388 | 389 | dx = sol_x[ẋ][1] 390 | drho = sol_x[ṙ][1] 391 | dm = sol_x[ṁ][1] 392 | 393 | @mtkbuild odesys = MassVolume(; dx, drho, dm) 394 | 395 | prob = ODEProblem(odesys, [], (0, t_end)) 396 | sol=solve(prob) 397 | 398 | plot(sol; idxs=odesys.vol.x, linewidth=2) 399 | plot!(sol_x; idxs=x) 400 | ``` 401 | 402 | ## Momentum Balance 403 | The next challenging aspect of hydraulic modeling is modeling flow through a pipe, which for compressible flow requires resolving the momentum balance equation. To derive the momentum balance we can draw a control volume (`cv`) in a pipe with area $A$, as shown in the figure below, and apply Newton's second law. Across this control volume from $x_1$ to $x_2$ the pressure will change from $p_1$ to $p_2$. Assuming this is written for an acausal component we put nodes at $p_1$ to $p_2$ which will have equal mass flow $\dot{m}$ entering and exiting the `cv`[^2]. 404 | 405 | [^2]: The Modelica Standard Library combines the mass and momentum balance to the same base class, therefore, mass flow in and out of the `cv` is not equal, which introduces an additional term to the lhs of the momentum balance: $ \frac{\partial \left( \rho u^2 A \right) }{\partial x} $ 406 | 407 | 408 | Now taking the sum of forces acting on the `cv` we have the pressure forces at each end as well as the viscous drag force from the pipe wall and the body force from gravity. The sum of forces is equal to the product of mass ($\rho V$) and flow acceleration ($\dot{u}$). 409 | 410 | ```math 411 | \rho V \dot{u} = (p_1 - p_2) A - F_{viscous} + \rho V g 412 | ``` 413 | 414 | where 415 | 416 | ```math 417 | \begin{align} 418 | F_{viscous} = A \frac{1}{2} \rho u^2 f \frac{L}{d_h} 419 | \end{align} 420 | ``` 421 | 422 | given $f$ is the fully developed flow pipe friction factor for a given shape, $L$ is the pipe length, and $d_h$ is the pipe hydraulic diameter. 423 | 424 | 425 | !!! note "Project Idea" 426 | the current implementation of this component in the ModelingToolkitStandardLibrary.jl does not include gravity force for this makes initialization challenging and will take some work to implement. 427 | 428 | 429 | The density $\rho$ is an average of $\rho_1$ and $\rho_2$. The velocity is also taken as an average of $u_1$ and $u_2$ 430 | 431 | ```math 432 | u_1 = \frac{\dot{m}}{\rho_1 A} 433 | ``` 434 | 435 | ```math 436 | u_2 = \frac{\dot{m}}{\rho_2 A} 437 | ``` 438 | 439 | ![momentum balance](../img/momentum_balance.svg) 440 | 441 | 442 | 443 | 444 | 445 | !!! note "Conservation of Momentum" 446 | the term ``\rho V \dot{u}`` introduces what is referd to as fluid inertia. This is what resolves the pressure wave propagation through a pipe. A classic wave propagation example in pipes is the "water hammer" effect. The full derivation for the flow velocity derivative is when deriving in 2 dimensions is 447 | ```math 448 | \frac{D \text{V}}{Dt} = \frac{\partial \text{V}}{\partial t} + \frac{\partial \text{V}}{\partial x} u + \frac{\partial \text{V}}{\partial z} w 449 | ``` 450 | where $\text{V}$ is the velocity vector, $u$ and $w$ are the flow components in $x$ and $y$ directions. In the ModelingToolkitStandardLibrary.jl this assumption is taken 451 | ```math 452 | \rho V \frac{D \text{V}}{Dt} \approx \frac{\partial \dot{m}}{\partial t} 453 | ``` 454 | 455 | 456 | !!! note "Project Idea" 457 | Implement a more detailed Conservation of Momentum using the standard derivation. One idea is to implement the MethodOfLines.jl to provide the derivative in $x$. 458 | 459 | 460 | ### Pipe Component 461 | To model a pipe for compressible flow, we can combine the mass balance and momentum balance components to give both mass storage and flow resistance. Furthermore, to provide a more accurate model that allows for wave propagation we can discretize the volume connected by node of equal pressure and mass flow. The diagram below shows an example of discretizing with 3 mass balance volumes and 2 momentum balance resistive elements. Note: the Modelica Standard Library does this in a different way, by combining the mass and momentum balance in a single base class. 462 | 463 | ![pipe](../img/pipe.svg) 464 | 465 | 466 | ### Dynamic Volume Component 467 | Both Modelica and SimScape model the actuator component with simply a uniform pressure volume component. The Modelica library defines the base fluids class around the assumption of constant length (see: [Object-Oriented Modeling of Thermo-Fluid Systems](https://elib.dlr.de/11988/1/otter2003-modelica-fluid.pdf)) and therefore adapting to a component that changes length is not possible. But in cases with long actuators with high dynamics the pressure is not at all uniform, therefore this detail cannot be ignored. Therefore, adding in the momentum balance to provide flow resistance and fluid inertia are necessary. The diagram below shows the design of a `DynamicVolume` component which includes both mass and momentum balance in addition to discretization by volume. The discretization is similar to the pipe, except the scheme becomes a bit more complicated with the moving wall ($x$). As the volume shrinks, the control volumes will also shrink, however not in unison, but one at a time. In this way, as the moving wall closes, the flow will come from the first volume $cv1$ and travel thru the full size remaining elements ($cv2$, $cv3$, etc.). After the first component length drops to zero, the next element will then start to shrink. 468 | 469 | ![volume](../img/volume.svg) 470 | 471 | This design has a flaw unfortunately, expanding the system for N=3 gives 472 | 473 | ![eqs1](../img/volume_eq1.png) 474 | 475 | What happens when transitioning from one cv to the next, if the moving wall velocity is significant, then an abrupt change occurs due to the $\rho_i \dot{x}$ term. This creates an unstable condition for the solver and results in poor quality/accuracy. To resolve this problem, the mass balance equation is split into 2 parts: mass balance 1 \& 2 476 | 477 | ```math 478 | \text{mass balance 1: } \dot{m}/A = \dot{\rho} x 479 | ``` 480 | 481 | ```math 482 | \text{mass balance 2: } \dot{m}/A = \rho \dot{x} 483 | ``` 484 | 485 | The below diagram explains how this component is constructed 486 | 487 | ![dynamic volume](../img/dynamic_volume.svg) 488 | 489 | Now the flows are simplified and are more numerically stable. The acausal connections then handle the proper summing of flows. 490 | 491 | ![eqs2](../img/volume_eq2.png) 492 | 493 | -------------------------------------------------------------------------------- /docs/src/lectures/lecture6.md: -------------------------------------------------------------------------------- 1 | # Debugging difficult stiff ODE/DAE models 2 | 3 | Below is a list of best practices to help avoid problems in model development and strategies that can be used to debug a problematic model. 4 | 5 | ## Best Practices 6 | In the world of programming, debugging a model has got to be the most challenging because all equations must be solved together. If any equation is wrong then not only will the model not solve, but there is very little that can be done to identify which equation is problematic. Therefore the best that we can do is implement best practices to ensure the model is correct from the beginning. 7 | 8 | ### Use acausal modeling (i.e. ModelingToolkit.jl) 9 | As has been shown ModelingToolkit.jl will help in many ways with model definition. One of the first programming practices that it enables is the DRY (Don't Repeat Yourself) principle. By defining components once and reusing them, this helps reduce the chance of human error. For example, when discovering a component level bug, it will be fixed at one source of truth and the fix will automatically propagate throughout. 10 | 11 | ### Start small and verify components 12 | In using acausal modeling, the main focus for ensuring well defined models lies mainly at the component level. Make sure to implement the rules of thumb discussed previously for number of equations and sign conventions. Each component should have a well defined unit test. When building your model start with the smallest subsystem possible and build from there. Attempting to build a full system model before checking the pieces is doomed to fail, leaving little to no insight into what went wrong. When a model fails to run, the error message will rarely give enough information to pinpoint the problem. The best tool for debugging is taking small incremental steps which allows one to identify which change caused the problem. 13 | 14 | ### Make sure equations match states 15 | It is not always the case, but for most models, the unsimplified system should give a match of equations and states. Let's take the pendulum problem for example 16 | 17 | ```@example l6 18 | using ModelingToolkit, DifferentialEquations, Plots 19 | using ModelingToolkit: t_nounits as t, D_nounits as D 20 | 21 | pars = @parameters m = 1 g = 1 L = 1 Φ=0 22 | 23 | vars = @variables begin 24 | x(t)=+L*cos(Φ) 25 | y(t)=-L*sin(Φ) 26 | dx(t)=0 27 | dy(t)=0 28 | λ(t) = 0 29 | end 30 | 31 | eqs = [ 32 | D(x) ~ dx 33 | D(y) ~ dy 34 | 35 | m*D(dx) ~ -λ*(x/L) 36 | m*D(dy) ~ -λ*(y/L) - m*g 37 | 38 | x^2 + y^2 ~ L^2 # algebraic constraint 39 | ] 40 | 41 | @named pendulum = ODESystem(eqs, t, vars, pars) 42 | nothing # hide 43 | ``` 44 | 45 | When we view the `ODESystem` we can see it has matching equations and states 46 | 47 | ```@repl l6 48 | pendulum 49 | ``` 50 | 51 | Note: when using `@mtkbuild` then `structural_simplify` is automatically called and we therefore cannot see the unsimplify system. Replace `@mtkbuild` with `@named` to generate an `ODESystem` without applying `structural_simplify`. 52 | 53 | 54 | ### Add compliance 55 | The pendulum problem as described above is derived assuming the following: 56 | 57 | - a massless perfectly stiff and rigid string/rod connected to the mass 58 | - a point mass 59 | - a frictionless mechanism 60 | 61 | If we attempt to solve this system we can see that it only solves up to the point that `x` crosses 0. 62 | 63 | ```@example l6 64 | sys = complete(structural_simplify(pendulum)) 65 | prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0, 10)) 66 | sol = solve(prob)# gives retcode: DtLessThanMin 67 | plot(sol; idxs=[x,y]) 68 | ``` 69 | 70 | The problem is rooted in the algebraic constraint which has `x^2` and `y^2`. Having exponents (squares or square roots) can often cause issues with numerical solutions. In this case the issue is that a unique solution cannot be found, `x` could be positive or negative. There are different solutions to this problem, however lets consider the concept of adding compliance. In reality is it really possible to have a massless, perfectly stiff and rigid string? No. Therefore let's consider adjusting the problem so the string has stiffness, which means we add `L` now as a variable. 71 | 72 | ```@example l6 73 | pars = @parameters m = 1 g = 1 L_0 = 1 Φ=0 k=1e6 74 | 75 | vars = @variables begin 76 | L(t)=L_0 77 | x(t)=+L*cos(Φ) 78 | y(t)=-L*sin(Φ) 79 | dx(t)=0 80 | dy(t)=0 81 | λ(t) = 0 82 | end 83 | 84 | eqs = [ 85 | D(x) ~ dx 86 | D(y) ~ dy 87 | 88 | m*D(dx) ~ -λ*(x/L) 89 | m*D(dy) ~ -λ*(y/L) - m*g 90 | 91 | x^2 + y^2 ~ L^2 # algebraic constraint 92 | 93 | λ ~ k*(L - L_0) # string stiffness 94 | ] 95 | 96 | @named stiffness_pendulum = ODESystem(eqs, t, vars, pars) 97 | sys = structural_simplify(stiffness_pendulum) 98 | prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0, 10)) 99 | sol = solve(prob)# Success 100 | plot(sol; idxs=[x,y]) 101 | ``` 102 | 103 | 104 | 105 | ### Try `dae_index_lowering()` 106 | In some cases we can apply `dae_index_lowering()` to further simplify the problem. In this case ModelingToolkit.jl finds a better form of the equations which can be solved without issue. 107 | 108 | ```@example l6 109 | sys = structural_simplify(dae_index_lowering(pendulum)) 110 | prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0, 10)) 111 | ref = solve(prob) 112 | plot(ref; idxs=x, label="dae_index_lowering") 113 | plot!(sol; idxs=x, label="compliance") 114 | ``` 115 | 116 | 117 | ### Design components with variable complexity/fidelity 118 | In general this can be achieved with parameters. For example, a *mass-spring-damper* system can easily become a *mass-damper* system by setting the spring stiffness to zero. But in other cases we might want to *structurally* variable the complexity. For example, the `ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible.Tube` component has 2 structural parameters: 119 | 120 | - `N` for discretization 121 | - `add_inertia` for including the wave equation 122 | 123 | Based on the inputs of these structural parameters, the number of generated equations will be different. Therefore, to start simple, one can set `N=0` and `add_inertia=false` to generate the simplest form of the problem. Solving this case first and ensuring the model physical behavior is correct is a good best practice before attempting to increase the fidelity of the model. 124 | 125 | 126 | ### Check values of parameters 127 | Another possible cause of problems in your model can come not from the equations, but from the parameters that are supplied to the equations. As discussed previously, models are stiff not because of their equations but because of the parameters. It's always a good idea to ensure your parameters match real life values to some degree. To ensure human error is not factoring in, it can be a good idea to use units (note ModelingToolkit v9 will be enforcing units using Uniful.jl). If you know all of your parameters are correct but still having issues, another debugging strategy is to reduce the energy input of your system. Rather than starting at 100%, start at 10%. This gives the model a better chance to solve and with a model solution this gives some insight to what the root cause problem might be. For example, if working with a hydraulic system, turn the input pressure down to 10%. 128 | 129 | ### Check acausal boundary conditions 130 | As discussed in Lecture 1, acausal connections always have a minimum of 2 variables. Therefore, acausal input (or boundary condition) components will need to pay attention to what should be done to both variables. As an example, refer to the hydraulic cylinder problem from Lecture 2 and consider the case where the position $x$ is supplied as the input boundary condition and the mass flow input $\dot{m}$ is set to an `Open()` boundary condition, thereby solving for $\dot{m}$ to give input $x$. 131 | 132 | ![example](../img/Example.svg) 133 | 134 | We can assemble the problem as 135 | 136 | ```@example l6 137 | import ModelingToolkitStandardLibrary.Mechanical.Translational as T 138 | import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC 139 | import ModelingToolkitStandardLibrary.Blocks as B 140 | 141 | include("volume.jl") # <-- missing Volume component from MTKSL (will be released in new version) 142 | 143 | function MassVolume(solves_force = true; name) 144 | 145 | pars = @parameters begin 146 | A = 0.01 #m² 147 | x₀ = 1.0 #m 148 | M = 10_000 #kg 149 | g = 9.807 #m/s² 150 | amp = 5e-2 #m 151 | f = 15 #Hz 152 | p_int=M*g/A 153 | dx=0 154 | drho=0 155 | dm=0 156 | end 157 | vars = [] 158 | systems = @named begin 159 | fluid = IC.HydraulicFluid(; density = 876, bulk_modulus = 1.2e9) 160 | mass = T.Mass(;v=dx,m=M,g=-g) 161 | vol = Volume(;area=A, x=x₀, p=p_int, dx, drho, dm) # <-- missing Volume component from MTKSL (will be released in new version) 162 | mass_flow = IC.Open(;p_int) 163 | position = T.Position(solves_force) 164 | position_input = B.TimeVaryingFunction(;f = t -> amp*sin(2π*t*f) + x₀) 165 | end 166 | 167 | eqs = [ 168 | connect(mass.flange, vol.flange, position.flange) 169 | connect(vol.port, mass_flow.port) 170 | connect(position.s, position_input.output) 171 | ] 172 | 173 | return ODESystem(eqs, t, vars, pars; systems, name) 174 | end 175 | 176 | @named odesys = MassVolume() 177 | nothing # hide 178 | ``` 179 | 180 | If we check the number of equations and states we see a mismatch! 181 | 182 | ```@repl l6 183 | odesys 184 | ``` 185 | 186 | The reason for the mismatch is that the input boundary condition `Position()` needs to know what to do about the connection variable for force `f`. In this problem, do we need a force introduced to the system to make the mass move as set by `Position()`? The answer is no, the force causing the mass to move is already given by the hydraulic pressure and gravity. If we look at the documentation for `Position()` we can see that it has a structural parameter `solves_force` which is defaulted to `true`. Therefore, to assemble the proper system we set this to `false` and now have a properly defined system 187 | 188 | ```@repl l6 189 | @named odesys = MassVolume(false) 190 | ``` 191 | 192 | 193 | ## Debugging Strategies 194 | It's very difficult to identify what is wrong with a model if it's not outputting any data. This section discusses ways to force a model solution. It's still possible that something with the model is wrong, but the best way to know that is to see what the equations are outputting. For example if the model is simulating negative pressure, but negative pressure is impossible, then this is a good clue of what is wrong with the model! The strategies for forcing a model solve will come from a simple hydraulic system that is attempting to start a hydraulic cylinder at a high pressure differential. See [ModelingToolkit Industrial Example](https://github.com/bradcarman/ModelingToolkitWebinar) for more information about the model. 195 | 196 | ```@example l6 197 | @mtkmodel System begin 198 | @parameters begin 199 | res₁₊Cₒ = 2.7 200 | res₁₊Aₒ = 0.00094 201 | res₁₊ρ₀ = 1000 202 | res₁₊p′ = 3.0e7 203 | res₂₊Cₒ = 2.7 204 | res₂₊Aₒ = 0.00094 205 | res₂₊ρ₀ = 1000 206 | res₂₊p′ = 0 207 | act₊p₁′ = 3.0e7 208 | act₊p₂′ = 0 209 | act₊vol₁₊A = 0.1 210 | act₊vol₁₊ρ₀ = 1000 211 | act₊vol₁₊β = 2.0e9 212 | act₊vol₁₊direction = -1 213 | act₊vol₁₊p′ = act₊p₁′ 214 | act₊vol₁₊x′ = 0.5 215 | act₊vol₂₊A = 0.1 216 | act₊vol₂₊ρ₀ = 1000 217 | act₊vol₂₊β = 2.0e9 218 | act₊vol₂₊direction = 1 219 | act₊vol₂₊p′ = act₊p₂′ 220 | act₊vol₂₊x′ = 0.5 221 | act₊mass₊m = 100 222 | act₊mass₊f′ = 0.1(-act₊p₁′ + act₊p₂′) 223 | src₊p′ = 3.0e7 224 | snk₊p′ = 0 225 | dmp₊c = 1000 226 | end 227 | @variables begin 228 | res₁₊ṁ(t) = 0 229 | res₁₊p₁(t) = res₁₊p′ 230 | res₁₊p₂(t) = res₁₊p′ 231 | res₁₊port₁₊p(t) = res₁₊p′ 232 | res₁₊port₁₊ṁ(t) = 0 233 | res₁₊port₂₊p(t) = res₁₊p′ 234 | res₁₊port₂₊ṁ(t) = 0 235 | res₂₊ṁ(t) = 0 236 | res₂₊p₁(t) = res₂₊p′ 237 | res₂₊p₂(t) = res₂₊p′ 238 | res₂₊port₁₊p(t) = res₂₊p′ 239 | res₂₊port₁₊ṁ(t) = 0 240 | res₂₊port₂₊p(t) = res₂₊p′ 241 | res₂₊port₂₊ṁ(t) = 0 242 | act₊port₁₊p(t) = act₊p₁′ 243 | act₊port₁₊ṁ(t) = 0 244 | act₊port₂₊p(t) = act₊p₂′ 245 | act₊port₂₊ṁ(t) = 0 246 | act₊vol₁₊p(t) = act₊vol₁₊p′ 247 | act₊vol₁₊x(t) = act₊vol₁₊x′ 248 | act₊vol₁₊ṁ(t) = 0 249 | act₊vol₁₊f(t) = act₊vol₁₊A * act₊vol₁₊p′ 250 | act₊vol₁₊ẋ(t) = 0 251 | act₊vol₁₊r(t) = act₊vol₁₊ρ₀ * (1 + act₊vol₁₊p′ / act₊vol₁₊β) 252 | act₊vol₁₊ṙ(t) = 0 253 | act₊vol₁₊port₊p(t) = act₊vol₁₊p′ 254 | act₊vol₁₊port₊ṁ(t) = 0 255 | act₊vol₁₊flange₊ẋ(t) = 0 256 | act₊vol₁₊flange₊f(t) = -act₊vol₁₊A * act₊vol₁₊direction * act₊vol₁₊p′ 257 | act₊vol₂₊p(t) = act₊vol₂₊p′ 258 | act₊vol₂₊x(t) = act₊vol₂₊x′ 259 | act₊vol₂₊ṁ(t) = 0 260 | act₊vol₂₊f(t) = act₊vol₂₊A * act₊vol₂₊p′ 261 | act₊vol₂₊ẋ(t) = 0 262 | act₊vol₂₊r(t) = act₊vol₂₊ρ₀ * (1 + act₊vol₂₊p′ / act₊vol₂₊β) 263 | act₊vol₂₊ṙ(t) = 0 264 | act₊vol₂₊port₊p(t) = act₊vol₂₊p′ 265 | act₊vol₂₊port₊ṁ(t) = 0 266 | act₊vol₂₊flange₊ẋ(t) = 0 267 | act₊vol₂₊flange₊f(t) = -act₊vol₂₊A * act₊vol₂₊direction * act₊vol₂₊p′ 268 | act₊mass₊f(t) = act₊mass₊f′ 269 | act₊mass₊x(t) = 0 270 | act₊mass₊ẋ(t) = 0 271 | act₊mass₊ẍ(t) = act₊mass₊f′ / act₊mass₊m 272 | act₊mass₊flange₊ẋ(t) = 0 273 | act₊mass₊flange₊f(t) = act₊mass₊f′ 274 | act₊flange₊ẋ(t) = 0 275 | act₊flange₊f(t) = 0 276 | src₊port₊p(t) = src₊p′ 277 | src₊port₊ṁ(t) = 0 278 | snk₊port₊p(t) = snk₊p′ 279 | snk₊port₊ṁ(t) = 0 280 | dmp₊flange₊ẋ(t) = 0 281 | dmp₊flange₊f(t) = 0 282 | end 283 | @equations begin 284 | res₁₊ṁ ~ res₁₊port₁₊ṁ 285 | res₁₊ṁ ~ -res₁₊port₂₊ṁ 286 | res₁₊p₁ ~ res₁₊port₁₊p 287 | res₁₊p₂ ~ res₁₊port₂₊p 288 | -res₁₊p₂ + res₁₊p₁ ~ 0.5res₁₊Cₒ * res₁₊ρ₀ * ((res₁₊ṁ / (res₁₊Aₒ * res₁₊ρ₀))^2) 289 | res₂₊ṁ ~ res₂₊port₁₊ṁ 290 | res₂₊ṁ ~ -res₂₊port₂₊ṁ 291 | res₂₊p₁ ~ res₂₊port₁₊p 292 | res₂₊p₂ ~ res₂₊port₂₊p 293 | -res₂₊p₂ + res₂₊p₁ ~ 0.5res₂₊Cₒ * res₂₊ρ₀ * ((res₂₊ṁ / (res₂₊Aₒ * res₂₊ρ₀))^2) 294 | D(act₊vol₁₊x) ~ act₊vol₁₊ẋ 295 | D(act₊vol₁₊r) ~ act₊vol₁₊ṙ 296 | act₊vol₁₊p ~ act₊vol₁₊port₊p 297 | act₊vol₁₊ṁ ~ act₊vol₁₊port₊ṁ 298 | act₊vol₁₊f ~ -act₊vol₁₊direction * act₊vol₁₊flange₊f 299 | act₊vol₁₊ẋ ~ act₊vol₁₊direction * act₊vol₁₊flange₊ẋ 300 | act₊vol₁₊r ~ act₊vol₁₊ρ₀ * (1 + act₊vol₁₊p / act₊vol₁₊β) 301 | act₊vol₁₊ṁ ~ act₊vol₁₊A * act₊vol₁₊ẋ * act₊vol₁₊r + act₊vol₁₊A * act₊vol₁₊x * act₊vol₁₊ṙ 302 | act₊vol₁₊f ~ act₊vol₁₊A * act₊vol₁₊p 303 | D(act₊vol₂₊x) ~ act₊vol₂₊ẋ 304 | D(act₊vol₂₊r) ~ act₊vol₂₊ṙ 305 | act₊vol₂₊p ~ act₊vol₂₊port₊p 306 | act₊vol₂₊ṁ ~ act₊vol₂₊port₊ṁ 307 | act₊vol₂₊f ~ -act₊vol₂₊direction * act₊vol₂₊flange₊f 308 | act₊vol₂₊ẋ ~ act₊vol₂₊direction * act₊vol₂₊flange₊ẋ 309 | act₊vol₂₊r ~ act₊vol₂₊ρ₀ * (1 + act₊vol₂₊p / act₊vol₂₊β) 310 | act₊vol₂₊ṁ ~ act₊vol₂₊A * act₊vol₂₊r * act₊vol₂₊ẋ + act₊vol₂₊A * act₊vol₂₊ṙ * act₊vol₂₊x 311 | act₊vol₂₊f ~ act₊vol₂₊A * act₊vol₂₊p 312 | D(act₊mass₊x) ~ act₊mass₊ẋ 313 | D(act₊mass₊ẋ) ~ act₊mass₊ẍ 314 | act₊mass₊f ~ act₊mass₊flange₊f 315 | act₊mass₊ẋ ~ act₊mass₊flange₊ẋ 316 | act₊mass₊m * act₊mass₊ẍ ~ act₊mass₊f 317 | src₊port₊p ~ src₊p′ 318 | snk₊port₊p ~ snk₊p′ 319 | dmp₊flange₊f ~ dmp₊c * dmp₊flange₊ẋ 320 | src₊port₊p ~ res₁₊port₁₊p 321 | 0 ~ res₁₊port₁₊ṁ + src₊port₊ṁ 322 | res₁₊port₂₊p ~ act₊port₁₊p 323 | 0 ~ act₊port₁₊ṁ + res₁₊port₂₊ṁ 324 | act₊port₂₊p ~ res₂₊port₁₊p 325 | 0 ~ act₊port₂₊ṁ + res₂₊port₁₊ṁ 326 | res₂₊port₂₊p ~ snk₊port₊p 327 | 0 ~ res₂₊port₂₊ṁ + snk₊port₊ṁ 328 | dmp₊flange₊ẋ ~ act₊flange₊ẋ 329 | 0 ~ act₊flange₊f + dmp₊flange₊f 330 | act₊port₁₊p ~ act₊vol₁₊port₊p 331 | 0 ~ act₊vol₁₊port₊ṁ - act₊port₁₊ṁ 332 | act₊port₂₊p ~ act₊vol₂₊port₊p 333 | 0 ~ -act₊port₂₊ṁ + act₊vol₂₊port₊ṁ 334 | act₊vol₁₊flange₊ẋ ~ act₊vol₂₊flange₊ẋ 335 | act₊vol₁₊flange₊ẋ ~ act₊mass₊flange₊ẋ 336 | act₊vol₁₊flange₊ẋ ~ act₊flange₊ẋ 337 | 0 ~ act₊vol₁₊flange₊f - act₊flange₊f + act₊vol₂₊flange₊f + act₊mass₊flange₊f 338 | end 339 | end 340 | 341 | @mtkbuild sys = System() 342 | prob = ODEProblem(sys, [], (0, 0.1)) 343 | sol = solve(prob) 344 | ``` 345 | 346 | As can be seen, when attempting to solve we get an `Unstable` return code. Let's explore strategies to fix the problem or find a forced numerical solution for debugging purposes. 347 | 348 | ### Initial Conditions 349 | First, let's check the initial conditions to see if at time 0 we are starting with zero residual for our algebraic equations. 350 | 351 | ```@example l6 352 | eqs = full_equations(sys) 353 | defs = ModelingToolkit.defaults(sys) 354 | residuals = Float64[] 355 | for eq in eqs 356 | if !ModelingToolkit.isdifferential(eq.lhs) 357 | push!(residuals, ModelingToolkit.fixpoint_sub(eq.rhs, defs)) 358 | end 359 | end 360 | residuals 361 | ``` 362 | 363 | As can be seen, we have a problem with our first algebraic equation, the residual is not zero! To solve this problem, ModelingToolkit v9 will be releasing a new feature to properly generate a non-linear system to calculate initial conditions. We also can apply the [Initialization Schemes](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/#Initialization-Schemes) provided from DifferentialEquations.jl. The `BrownFullBasicInit` is the default algorithm used, and this did not work for our problem, so we will move to the `ShampineCollocationInit`. 364 | 365 | ```@example l6 366 | dt = 1e-7 367 | sol = solve(prob; initializealg=ShampineCollocationInit(dt)) 368 | ``` 369 | 370 | The `ShampineCollocationInit` solves the initial conditions by essentially taking a small step forward in time (`dt`) and then updating the initial conditions with that solve. If this doesn't work, we can instead do this manually. 371 | 372 | ```@example l6 373 | prob = ODEProblem(sys, [], (0, dt)) 374 | sol = solve(prob, ImplicitEuler(nlsolve=NLNewton(check_div=false, always_new=true, relax=4/10, max_iter=100)); dt, adaptive=false) 375 | 376 | # update u0 with the ImplicitEuler non-adaptive step 377 | prob′ = ODEProblem(sys, sol[2], (0, 0.1)) 378 | sol = solve(prob′); 379 | plot(sol; idxs=sys.act₊mass₊ẋ) 380 | ``` 381 | 382 | As can be seen, now we have a successful solve. We can see the change to the initial conditions is very minimal. As can be seen, the solver needs the derivative terms to be offset by a small amount. 383 | 384 | ```@example l6 385 | println(join(["$s : $(round(x; digits=3)) -> $(round(y; digits=3))" for (s,x,y) in zip(unknowns(sys), prob.u0, prob′.u0)],'\n')) 386 | ``` 387 | 388 | Another strategy that can help issues with initial conditions is to offset or perturb any initial conditions from 0 by a small value. 389 | 390 | ### Adjust tolerance 391 | Here we get a solve by increasing the `abstol` and `reltol` to very large values. This is therefore understood to give us a very low resolution solution that is far from the true solution, but we can now at least see if the model is calculating generally correct values, at least with the correct sign. Here we expect the `act₊mass₊ẋ` to be around -1 and that's exactly what we get. However, as can be seen the tolerance is too open to resolve the dynamics. 392 | 393 | ```@example l6 394 | prob = ODEProblem(sys, [], (0, 0.1)) 395 | sol = solve(prob, ImplicitEuler(); abstol=10000.0, reltol=100.0) 396 | plot(sol; idxs=sys.act₊mass₊ẋ) 397 | ``` 398 | 399 | 400 | ### Turn off adaptivity 401 | Another strategy similar to adjusting tolerance is to turn off adaptivity. This means we can no longer guarantee tolerance, but we can at least adjust the time step such that it's small enough to give a good solution. A good practice is to continue to decrease `dt` until the solution converges (i.e. stops changing). Without adaptivity this is another way to help ensure solution accuracy. 402 | 403 | ```@example l6 404 | prob = ODEProblem(sys, [], (0, 0.1)) 405 | sol = solve(prob, ImplicitEuler(nlsolve=NLNewton(check_div=false, always_new=true, relax=4/10, max_iter=100)); initializealg=NoInit(), adaptive=false, dt=1e-6) 406 | plot(sol; idxs=sys.act₊mass₊ẋ) 407 | ``` 408 | 409 | Note the use of keywords: 410 | - `check_div=false`: ensures the problem doesn't exit early because of divergence 411 | - `always_new=true`: ensures Jacobian is always updated to give a more robust solve 412 | - `relax`: for relaxation of Newton iterations to give a more robust solve 413 | - `max_iter` to ensure enough iterations are available 414 | 415 | ### Jacobian generation 416 | There are 3 different ways to calculate the Jacobian from ModelingToolkit: 417 | 418 | 1. analytically, by using `jac=true` keyword given to `ODEProblem` 419 | 2. automatically with automatic differentiation using `autodiff=true` given to the solver algorithms that use Jacobians 420 | 3. automatically with finite differencing using `autodiff=false` 421 | 422 | Each choice offers different levels of numerical accuracy (in order from highest to lowest) and computational expense. In some cases choosing a less numerical accurate Jacobian can actually help provide a solution for very stiff problems. 423 | 424 | 425 | ### DAE conversion to ODE 426 | If all else fails, one concept that may work is to convert the DAE to an ODE by implementing a small epsilon ($\epsilon$) term such that when $\epsilon=0$ the problem is a DAE and when $\epsilon$ is small the problem approximates an ODE. For example, consider the DAE 427 | 428 | ```math 429 | \dot{x} = y \\ 430 | 0 = x + y 431 | ``` 432 | 433 | We can adjust the 2nd equation to approximate an ODE like 434 | 435 | ```math 436 | \dot{x} = y \\ 437 | \epsilon \cdot \dot{y} = x + y 438 | ``` 439 | 440 | The below function can create such a transformation. 441 | 442 | ```@example l6 443 | using Setfield 444 | 445 | function dae_to_ode(sys::ODESystem) 446 | 447 | are_vars_equal(var1, var2) = string(var1) == string(var2) 448 | 449 | defs = ModelingToolkit.defaults(sys) 450 | pars = parameters(sys) 451 | sts = unknowns(sys) 452 | eqs = equations(sys) 453 | iv = ModelingToolkit.independent_variable(sys) 454 | D = Differential(iv) 455 | 456 | diff_vars = [] 457 | for eq in eqs 458 | eq_sts = [] 459 | ModelingToolkit.vars!(eq_sts, eq) 460 | diffs = ModelingToolkit.isdifferential.(eq_sts) 461 | if any(diffs) 462 | diff = eq_sts[diffs] |> first 463 | diff_var = ModelingToolkit.arguments(diff) |> first 464 | push!(diff_vars, diff_var) 465 | end 466 | end 467 | 468 | diffs = setdiff(sts, diff_vars) 469 | 470 | @parameters ϵ 471 | j = 1 472 | neqs = Equation[] 473 | for eq in eqs 474 | if ModelingToolkit._iszero(eq.lhs) 475 | push!(neqs, D(diffs[j]) ~ eq.rhs/ϵ) 476 | j+=1 477 | else 478 | push!(neqs, eq) 479 | end 480 | end 481 | 482 | @set! sys.eqs = neqs 483 | @set! sys.ps = [ModelingToolkit.unwrap(ϵ); pars] 484 | @set! sys.defaults = Dict(ϵ => -1e-12, pairs(defs)...) 485 | 486 | return sys 487 | end 488 | nothing # hide 489 | ``` 490 | 491 | Implementing this for the hydraulic system works well, giving an adaptive time solution using `Tsit5` 492 | 493 | ```julia 494 | odesys = dae_to_ode(sys) 495 | prob = ODEProblem(odesys, [], (0,0.1)) 496 | sol = solve(prob) 497 | plot(sol; idxs=sys.act₊mass₊ẋ) 498 | ``` 499 | 500 | Note this problem, as we've seen, has a lot of trouble with initialization. Note how the first 200 steps are taken with a very small time step. The `Tsit5` solver is able to successfully push through the model initialization and then solve the remaining steps at a reasonable time step. 501 | 502 | ```julia 503 | plot(diff(sol.t)) 504 | ``` 505 | 506 | --------------------------------------------------------------------------------