Skip to content

Commit 2092c4b

Browse files
Merge pull request #3285 from AayushSabharwal/as/analysis-points
feat: add analysis points
2 parents 6143897 + 69e05f5 commit 2092c4b

22 files changed

+1602
-18
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ MTKBifurcationKitExt = "BifurcationKit"
7373
MTKChainRulesCoreExt = "ChainRulesCore"
7474
MTKDeepDiffsExt = "DeepDiffs"
7575
MTKHomotopyContinuationExt = "HomotopyContinuation"
76-
MTKLabelledArraysExt = "LabelledArrays"
7776
MTKInfiniteOptExt = "InfiniteOpt"
77+
MTKLabelledArraysExt = "LabelledArrays"
7878

7979
[compat]
8080
AbstractTrees = "0.3, 0.4"
@@ -117,6 +117,7 @@ Latexify = "0.11, 0.12, 0.13, 0.14, 0.15, 0.16"
117117
Libdl = "1"
118118
LinearAlgebra = "1"
119119
MLStyle = "0.4.17"
120+
ModelingToolkitStandardLibrary = "2.19"
120121
NaNMath = "0.3, 1"
121122
NonlinearSolve = "4.3"
122123
OffsetArrays = "1"

docs/Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[deps]
22
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
33
BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665"
4+
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
45
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
5-
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
66
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
77
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
88
DynamicQuantities = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
99
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1010
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
11+
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
1112
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
1213
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
1314
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
@@ -26,11 +27,11 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
2627
BenchmarkTools = "1.3"
2728
BifurcationKit = "0.4"
2829
DataInterpolations = "6.5"
29-
DifferentialEquations = "7.6"
3030
Distributions = "0.25"
3131
Documenter = "1"
3232
DynamicQuantities = "^0.11.2, 0.12, 1"
3333
ModelingToolkit = "8.33, 9"
34+
ModelingToolkitStandardLibrary = "2.19"
3435
NonlinearSolve = "3, 4"
3536
Optim = "1.7"
3637
Optimization = "3.9, 4"

docs/make.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ makedocs(sitename = "ModelingToolkit.jl",
2525
modules = [ModelingToolkit],
2626
clean = true, doctest = false, linkcheck = true,
2727
warnonly = [:docs_block, :missing_docs, :cross_references],
28-
linkcheck_ignore = ["https://epubs.siam.org/doi/10.1137/0903023"],
28+
linkcheck_ignore = [
29+
"https://epubs.siam.org/doi/10.1137/0903023",
30+
# this link tends to fail linkcheck stochastically and often takes much longer to succeed
31+
# even in the browser it takes ages
32+
"http://www.scholarpedia.org/article/Differential-algebraic_equations"
33+
],
2934
format = Documenter.HTML(;
3035
assets = ["assets/favicon.ico"],
3136
mathengine,

docs/pages.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ pages = [
1313
"tutorials/bifurcation_diagram_computation.md",
1414
"tutorials/SampledData.md",
1515
"tutorials/domain_connections.md",
16-
"tutorials/callable_params.md"],
16+
"tutorials/callable_params.md",
17+
"tutorials/linear_analysis.md"],
1718
"Examples" => Any[
1819
"Basic Examples" => Any["examples/higher_order.md",
1920
"examples/spring_mass.md",

docs/src/basics/Composition.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ x0 = [decay1.x => 1.0
5656
p = [decay1.a => 0.1
5757
decay2.a => 0.2]
5858
59-
using DifferentialEquations
59+
using OrdinaryDiffEq
6060
prob = ODEProblem(simplified_sys, x0, (0.0, 100.0), p)
6161
sol = solve(prob, Tsit5())
6262
sol[decay2.f]

docs/src/examples/perturbation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ These are the ODEs we want to solve. Now construct an `ODESystem`, which automat
4949
To solve the `ODESystem`, we generate an `ODEProblem` with initial conditions $x(0) = 0$, and $ẋ(0) = 1$, and solve it:
5050

5151
```@example perturbation
52-
using DifferentialEquations
52+
using OrdinaryDiffEq
5353
u0 = Dict([unknowns(sys) .=> 0.0; D(y[0]) => 1.0]) # nonzero initial velocity
5454
prob = ODEProblem(sys, u0, (0.0, 3.0))
5555
sol = solve(prob)

docs/src/examples/sparse_jacobians.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ First, let's start out with an implementation of the 2-dimensional Brusselator
1111
partial differential equation discretized using finite differences:
1212

1313
```@example sparsejac
14-
using DifferentialEquations, ModelingToolkit
14+
using OrdinaryDiffEq, ModelingToolkit
1515
1616
const N = 32
1717
const xyd_brusselator = range(0, stop = 1, length = N)

docs/src/examples/spring_mass.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ In this tutorial, we will build a simple component-based model of a spring-mass
55
## Copy-Paste Example
66

77
```@example component
8-
using ModelingToolkit, Plots, DifferentialEquations, LinearAlgebra
8+
using ModelingToolkit, Plots, OrdinaryDiffEq, LinearAlgebra
99
using ModelingToolkit: t_nounits as t, D_nounits as D
1010
using Symbolics: scalarize
1111

docs/src/tutorials/acausal_components.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ equalities before solving. Let's see this in action.
2020
## Copy-Paste Example
2121

2222
```@example acausal
23-
using ModelingToolkit, Plots, DifferentialEquations
23+
using ModelingToolkit, Plots, OrdinaryDiffEq
2424
using ModelingToolkit: t_nounits as t, D_nounits as D
2525
2626
@connector Pin begin

docs/src/tutorials/linear_analysis.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Linear Analysis
2+
3+
Linear analysis refers to the process of linearizing a nonlinear model and analysing the resulting linear dynamical system. To facilitate linear analysis, ModelingToolkit provides the concept of an [`AnalysisPoint`](@ref), which can be inserted in-between two causal blocks (such as those from `ModelingToolkitStandardLibrary.Blocks` sub module). Once a model containing analysis points is built, several operations are available:
4+
5+
- [`get_sensitivity`](@ref) get the [sensitivity function (wiki)](https://en.wikipedia.org/wiki/Sensitivity_(control_systems)), $S(s)$, as defined in the field of control theory.
6+
- [`get_comp_sensitivity`](@ref) get the complementary sensitivity function $T(s) : S(s)+T(s)=1$.
7+
- [`get_looptransfer`](@ref) get the (open) loop-transfer function where the loop starts and ends in the analysis point. For a typical simple feedback connection with a plant $P(s)$ and a controller $C(s)$, the loop-transfer function at the plant output is $P(s)C(s)$.
8+
- [`linearize`](@ref) can be called with two analysis points denoting the input and output of the linearized system.
9+
- [`open_loop`](@ref) return a new (nonlinear) system where the loop has been broken in the analysis point, i.e., the connection the analysis point usually implies has been removed.
10+
11+
An analysis point can be created explicitly using the constructor [`AnalysisPoint`](@ref), or automatically when connecting two causal components using `connect`:
12+
13+
```julia
14+
connect(comp1.output, :analysis_point_name, comp2.input)
15+
```
16+
17+
A single output can also be connected to multiple inputs:
18+
19+
```julia
20+
connect(comp1.output, :analysis_point_name, comp2.input, comp3.input, comp4.input)
21+
```
22+
23+
!!! warning "Causality"
24+
25+
Analysis points are *causal*, i.e., they imply a directionality for the flow of information. The order of the connections in the connect statement is thus important, i.e., `connect(out, :name, in)` is different from `connect(in, :name, out)`.
26+
27+
The directionality of an analysis point can be thought of as an arrow in a block diagram, where the name of the analysis point applies to the arrow itself.
28+
29+
```
30+
┌─────┐ ┌─────┐
31+
│ │ name │ │
32+
│ out├────────►│in │
33+
│ │ │ │
34+
└─────┘ └─────┘
35+
```
36+
37+
This is signified by the name being the middle argument to `connect`.
38+
39+
Of the above mentioned functions, all except for [`open_loop`](@ref) return the output of [`ModelingToolkit.linearize`](@ref), which is
40+
41+
```julia
42+
matrices, simplified_sys = linearize(...)
43+
# matrices = (; A, B, C, D)
44+
```
45+
46+
i.e., `matrices` is a named tuple containing the matrices of a linear state-space system on the form
47+
48+
```math
49+
\begin{aligned}
50+
\dot x &= Ax + Bu\\
51+
y &= Cx + Du
52+
\end{aligned}
53+
```
54+
55+
## Example
56+
57+
The following example builds a simple closed-loop system with a plant $P$ and a controller $C$. Two analysis points are inserted, one before and one after $P$. We then derive a number of sensitivity functions and show the corresponding code using the package ControlSystemBase.jl
58+
59+
```@example LINEAR_ANALYSIS
60+
using ModelingToolkitStandardLibrary.Blocks, ModelingToolkit
61+
@named P = FirstOrder(k = 1, T = 1) # A first-order system with pole in -1
62+
@named C = Gain(-1) # A P controller
63+
t = ModelingToolkit.get_iv(P)
64+
eqs = [connect(P.output, :plant_output, C.input) # Connect with an automatically created analysis point called :plant_output
65+
connect(C.output, :plant_input, P.input)]
66+
sys = ODESystem(eqs, t, systems = [P, C], name = :feedback_system)
67+
68+
matrices_S = get_sensitivity(sys, :plant_input)[1] # Compute the matrices of a state-space representation of the (input)sensitivity function.
69+
matrices_T = get_comp_sensitivity(sys, :plant_input)[1]
70+
```
71+
72+
Continued linear analysis and design can be performed using ControlSystemsBase.jl.
73+
We create `ControlSystemsBase.StateSpace` objects using
74+
75+
```@example LINEAR_ANALYSIS
76+
using ControlSystemsBase, Plots
77+
S = ss(matrices_S...)
78+
T = ss(matrices_T...)
79+
bodeplot([S, T], lab = ["S" "" "T" ""], plot_title = "Bode plot of sensitivity functions",
80+
margin = 5Plots.mm)
81+
```
82+
83+
The sensitivity functions obtained this way should be equivalent to the ones obtained with the code below
84+
85+
```@example LINEAR_ANALYSIS_CS
86+
using ControlSystemsBase
87+
P = tf(1.0, [1, 1]) |> ss
88+
C = 1 # Negative feedback assumed in ControlSystems
89+
S = sensitivity(P, C) # or feedback(1, P*C)
90+
T = comp_sensitivity(P, C) # or feedback(P*C)
91+
```
92+
93+
We may also derive the loop-transfer function $L(s) = P(s)C(s)$ using
94+
95+
```@example LINEAR_ANALYSIS
96+
matrices_L = get_looptransfer(sys, :plant_output)[1]
97+
L = ss(matrices_L...)
98+
```
99+
100+
which is equivalent to the following with ControlSystems
101+
102+
```@example LINEAR_ANALYSIS_CS
103+
L = P * (-C) # Add the minus sign to build the negative feedback into the controller
104+
```
105+
106+
To obtain the transfer function between two analysis points, we call `linearize`
107+
108+
```@example LINEAR_ANALYSIS
109+
using ModelingToolkit # hide
110+
matrices_PS = linearize(sys, :plant_input, :plant_output)[1]
111+
```
112+
113+
this particular transfer function should be equivalent to the linear system `P(s)S(s)`, i.e., equivalent to
114+
115+
```@example LINEAR_ANALYSIS_CS
116+
feedback(P, C)
117+
```
118+
119+
### Obtaining transfer functions
120+
121+
A statespace system from [ControlSystemsBase](https://juliacontrol.github.io/ControlSystems.jl/stable/man/creating_systems/) can be converted to a transfer function using the function `tf`:
122+
123+
```@example LINEAR_ANALYSIS_CS
124+
tf(S)
125+
```
126+
127+
## Gain and phase margins
128+
129+
Further linear analysis can be performed using the [analysis methods from ControlSystemsBase](https://juliacontrol.github.io/ControlSystems.jl/stable/lib/analysis/). For example, calculating the gain and phase margins of a system can be done using
130+
131+
```@example LINEAR_ANALYSIS_CS
132+
margin(P)
133+
```
134+
135+
(they are infinite for this system). A Nyquist plot can be produced using
136+
137+
```@example LINEAR_ANALYSIS_CS
138+
nyquistplot(P)
139+
```
140+
141+
## Index
142+
143+
```@index
144+
Pages = ["linear_analysis.md"]
145+
```
146+
147+
```@autodocs
148+
Modules = [ModelingToolkit]
149+
Pages = ["systems/analysis_points.jl"]
150+
Order = [:function, :type]
151+
Private = false
152+
```

0 commit comments

Comments
 (0)