Skip to content

Commit 0a736e0

Browse files
authored
Merge pull request #114 from bradcarman/bgc/multi2d
Start to Translational library with both Velocity and Position based connectors
2 parents 28ad75d + 516f3ab commit 0a736e0

25 files changed

+1750
-3
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
1212

1313
[compat]
1414
IfElse = "0.1"
15-
ModelingToolkit = "8.24"
15+
ModelingToolkit = "8.26"
1616
OffsetArrays = "1"
1717
Symbolics = "4.9"
1818
julia = "1.6"

docs/Project.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[deps]
22
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
3+
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
34
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
45
IfElse = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
56
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"

docs/pages.jl

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pages = [
66
"Thermal Conduction Model" => "tutorials/thermal_model.md",
77
"DC Motor with Speed Controller" => "tutorials/dc_motor_pi.md",
88
],
9+
"About Acausal Connections" => "connectors/connections.md",
910
"API" => [
1011
"Basic Blocks" => "API/blocks.md",
1112
"Electrical Components" => "API/electrical.md",

docs/src/API/linear_analysis.md

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ L = P*(-C) # Add the minus sign to build the negative feedback into the controll
7979

8080
To obtain the transfer function between two analysis points, we call `linearize`
8181
```@example LINEAR_ANALYSIS
82+
using ModelingToolkit # hide
8283
matrices_PS = linearize(sys, :plant_input, :plant_output)[1]
8384
```
8485
this particular transfer function should be equivalent to the linear system `P(s)S(s)`, i.e., equivalent to

docs/src/connectors/connections.md

+356
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# Introduction
2+
In Physical Network Acausal modeling each physical domain must define a **connector** to combine model components. Each physical domain **connector** defines a minimum of 2 variables, one which is called a *Through* variable, and one which is called an *Across* variable. Both Modelica and SimScape define these variables in the same way:
3+
4+
- [Modelica Connectors](https://mbe.modelica.university/components/connectors/#acausal-connection)
5+
- [SimScape Connectors](https://www.mathworks.com/help/simscape/ug/basic-principles-of-modeling-physical-networks.html#bq89sba-6)
6+
7+
However, the standard libraries differ on the selection of the Across variable for the Mechanical Translation and Rotation libraries, Modelica choosing position and angle and SimScape choosing velocity and angular velocity, respectively for Translation and Rotation. Modelica describes their decision [here](https://mbe.modelica.university/components/connectors/simple_domains/). In summary they would like to provide less integration in the model to avoid lossy numerical behavior, but this decision assumes the lowest order derivative is needed by the model. Numerically it is possible to define the connector either way, but there are some consequences to this decision, and therefore we will study them in detail here as they relate to ModelingToolkit.
8+
9+
# Through and Across Variable Theory
10+
### General
11+
The idea behind the selection of the **through** variable is that it should be a time derivative of some conserved quantity. The conserved quantity should be expressed by the **across** variable. In general terms the physical system is given by
12+
13+
- Energy Dissipation & Flow:
14+
15+
```math
16+
\begin{aligned}
17+
\partial {\color{blue}{across}} / \partial t \cdot c_1 = {\color{green}{through}} \\
18+
{\color{green}{through}} \cdot c_2 = {\color{blue}{across}}
19+
\end{aligned}
20+
```
21+
22+
23+
24+
### Electrical
25+
For the Electrical domain the across variable is *voltage* and the through variable *current*. Therefore
26+
27+
- Energy Dissipation:
28+
```math
29+
\partial {\color{blue}{voltage}} / \partial t \cdot capacitance = {\color{green}{current}}
30+
```
31+
32+
- Flow:
33+
```math
34+
{\color{green}{current}} \cdot resistance = {\color{blue}{voltage}}
35+
```
36+
37+
### Translational
38+
For the translation domain, choosing *velocity* for the across variable and *force* for the through gives
39+
40+
- Energy Dissipation:
41+
```math
42+
\partial {\color{blue}{velocity}} / \partial t \cdot mass = {\color{green}{force}}
43+
```
44+
45+
- Flow:
46+
```math
47+
{\color{green}{force}} \cdot (1/damping) = {\color{blue}{velocity}}
48+
```
49+
50+
The diagram here shows the similarity of problems in different physical domains.
51+
52+
![Through and Across Variables](through_across.png)
53+
54+
### Translational Connector using *Position* Across Variable
55+
Now, if we choose *position* for the across variable, a similar relationship can be established, but the patern must be broken.
56+
57+
- Energy Dissipation:
58+
```math
59+
\partial^2 {\color{blue}{position}} / \partial t^2 \cdot mass = {\color{green}{force}}
60+
```
61+
62+
- Flow:
63+
```math
64+
{\color{green}{force}} \cdot (1/damping) = \partial {\color{blue}{position}} / \partial t
65+
```
66+
67+
As can be seen, we must now establish a higher order derivative to define the Energy Dissipation and Flow equations, requiring an extra equation, as will be shown in the example below.
68+
69+
# Examples
70+
### Electrical Domain
71+
We can generate the above relationship with ModelingToolkit and the ModelingToolkitStandardLibrary using 3 blocks:
72+
73+
- Capacitor: for energy storage with initial voltage = 1V
74+
- Resistor: for energy flow
75+
- Ground: for energy sink
76+
77+
As can be seen, this will give a 1 equation model matching our energy dissipation relationship
78+
79+
```@example connections
80+
using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, DifferentialEquations
81+
using Plots
82+
83+
@parameters t
84+
85+
@named resistor = Resistor(R = 1)
86+
@named capacitor = Capacitor(C = 1)
87+
@named ground = Ground()
88+
89+
eqs = [
90+
connect(capacitor.p, resistor.p)
91+
connect(resistor.n, ground.g, capacitor.n)
92+
]
93+
94+
@named model = ODESystem(eqs, t; systems=[resistor, capacitor, ground])
95+
96+
sys = structural_simplify(model)
97+
98+
println.(equations(sys))
99+
nothing # hide
100+
```
101+
102+
The solution shows what we would expect, a non-linear disipation of voltage and releated decrease in current flow...
103+
104+
```@example connections
105+
prob = ODEProblem(sys, [1.0], (0, 10.0), [])
106+
sol = solve(prob)
107+
108+
p1=plot(sol, idxs=[capacitor.v])
109+
p2=plot(sol, idxs=[resistor.i])
110+
plot(p1,p2)
111+
savefig("electrical.png"); nothing # hide
112+
```
113+
114+
![Plot of Electrical Example](electrical.png)
115+
116+
### Mechanical Translational Domain
117+
#### Across Variable = velocity
118+
Now using the Translational library based on velocity, we can see the same relationship with a system reduced to a single equation, using the components:
119+
120+
- Body (i.e. moving mass): for kinetic energy storage with an initial velocity = 1m/s
121+
- Damper: for energy flow
122+
- Fixed: for energy sink
123+
124+
```@example connections
125+
using ModelingToolkitStandardLibrary
126+
const TV = ModelingToolkitStandardLibrary.Mechanical.Translational
127+
128+
@named damping = TV.Damper(d=1, v_a_0=1)
129+
@named body = TV.Mass(m=1, v_0=1)
130+
@named ground = TV.Fixed()
131+
132+
eqs = [
133+
connect(damping.flange_a, body.flange)
134+
connect(ground.flange, damping.flange_b)
135+
]
136+
137+
@named model = ODESystem(eqs, t; systems=[damping, body, ground])
138+
139+
sys = structural_simplify(model)
140+
141+
println.(full_equations(sys))
142+
nothing # hide
143+
```
144+
145+
As expected we have a similar solution...
146+
```@example connections
147+
prob = ODEProblem(sys, [], (0, 10.0), [])
148+
sol_v = solve(prob)
149+
150+
p1=plot(sol_v, idxs=[body.v])
151+
p2=plot(sol_v, idxs=[damping.f])
152+
plot(p1,p2)
153+
savefig("mechanical_velocity.png"); nothing # hide
154+
```
155+
156+
![Plot of Mechanical (Velocity Based) Example](mechanical_velocity.png)
157+
158+
159+
#### Across Variable = position
160+
Now, let's consider the position based approach. We can build the same model with the same components. As can be seen, we now end of up with 2 equations, because we need to relate the lower derivative (position) to force (with acceleration).
161+
162+
```@example connections
163+
const TP = ModelingToolkitStandardLibrary.Mechanical.TranslationalPosition
164+
165+
@named damping = TP.Damper(d=1, v_a_0=1)
166+
@named body = TP.Mass(m=1, v_0=1)
167+
@named ground = TP.Fixed(s_0=0)
168+
169+
eqs = [
170+
connect(damping.flange_a, body.flange)
171+
connect(ground.flange, damping.flange_b)
172+
]
173+
174+
@named model = ODESystem(eqs, t; systems=[damping, body, ground])
175+
176+
sys = structural_simplify(model)
177+
178+
println.(full_equations(sys))
179+
nothing # hide
180+
```
181+
As can be seen, we get exactly the same result. The only difference here is that we are solving an extra equation, which allows us to plot the body position as well.
182+
183+
```@example connections
184+
prob = ODEProblem(sys, [], (0, 10.0), [])
185+
sol_p = solve(prob)
186+
187+
p1=plot(sol_p, idxs=[body.v])
188+
p2=plot(sol_p, idxs=[damping.f])
189+
p3=plot(sol_p, idxs=[body.s])
190+
191+
plot(p1,p2,p3)
192+
savefig("mechanical_position.png"); nothing # hide
193+
```
194+
195+
![Plot of Mechanical (Velocity Based) Example](mechanical_position.png)
196+
197+
The question then arises, can the position be plotted when using the Mechanical Translational Domain based on the Velocity Across variable? Yes, we can! There are 2 solutions:
198+
199+
1. the `Mass` component will add the position variable when the `s_0` parameter is used to set an initial position. Otherwise the position is not tracked by the component.
200+
201+
```julia
202+
@named body = TV.Mass(m=1, v_0=1, s_0=0)
203+
```
204+
205+
2. implement a `PositionSensor`
206+
TODO: Implement Translation Sensors
207+
208+
209+
Either option will produce the same result regardless of which across variable is used. If the same result is given, why are both options included in the Standard Library, what are the differences? These differences will be discussed next so that an informed decision can be made about which domain is best for your model.
210+
211+
# Mechanical/Translational Library Differences (Velocity vs. Position Connectors)
212+
## Initialization
213+
The main difference between `ModelingToolkitStandardLibrary.Mechanical.Translational` and `ModelingToolkitStandardLibrary.Mechanical.TranslationalPosition` is how they are initialized. In the `ModelingToolkitStandardLibrary` initialization parameters are defined at the component level, so we simply need to be careful to set the correct initial conditions for the domain that it used. Let's use the following example problem to explain the differences.
214+
215+
![Example Mechanical Model](model.png)
216+
217+
In this problem we have a mass, spring, and damper which are connected to a fixed point. Let's see how each component is defined.
218+
219+
#### Damper
220+
The damper will connect the flange/flange 1 (`flange_a`) to the mass, and flange/flange 2 (`flange_b`) to the fixed point. For both position and velocity based domains, we set the damping constant `d=1` and `v_a_0=1` and leave the default for `v_b_0` at 0. For the position domain we also need to set the initial positions for `flange_a` and `flange_b`.
221+
222+
```@example connections
223+
@named dv = TV.Damper(d=1, v_a_0=1)
224+
@named dp = TP.Damper(d=1, v_a_0=1, s_a_0=3, s_b_0=1)
225+
nothing # hide
226+
```
227+
228+
#### Spring
229+
The spring will connect the flange/flange 1 (`flange_a`) to the mass, and flange/flange 2 (`flange_b`) to the fixed point. For both position and velocity based domains, we set the spring constant `k=1`. The velocity domain then requires the initial velocity `v_a_0` and initial spring stretch `delta_s_0`. The position domain instead needs the initial positions for `flange_a` and `flange_b` and the natural spring length `l`.
230+
231+
```@example connections
232+
@named sv = TV.Spring(k=1, v_a_0=1, delta_s_0=1)
233+
@named sp = TP.Spring(k=1, s_a_0=3, s_b_0=1, l=1)
234+
nothing # hide
235+
```
236+
237+
#### Mass
238+
For both position and velocity based domains, we set the mass `m=1` and initial velocity `v_0=1`. Like the damper, the position domain requires the position initial conditions set as well.
239+
240+
```@example connections
241+
@named bv = TV.Mass(m=1, v_0=1)
242+
@named bp = TP.Mass(m=1, v_0=1, s_0=3)
243+
nothing # hide
244+
```
245+
246+
#### Fixed
247+
Here the velocity domain requires no initial condition, but for our model to work as defined we must set the position domain component to the correct intital position.
248+
249+
```@example connections
250+
@named gv = TV.Fixed()
251+
@named gp = TP.Fixed(s_0=1)
252+
nothing # hide
253+
```
254+
255+
### Comparison
256+
As can be seen, the position based domain requires more initial condition information to be properly defined since the absolute position information is required. Thereore based on the model being described, it may be more natural to choose one domain over the other.
257+
258+
Let's define a quick function to simplify and solve the 2 different systems. Note we will solve with a fixed time step and a set tolerance to compare the numerical differences.
259+
260+
```@example connections
261+
function simplify_and_solve(damping, spring, body, ground)
262+
263+
eqs = [connect(spring.flange_a, body.flange, damping.flange_a)
264+
connect(spring.flange_b, damping.flange_b, ground.flange)
265+
]
266+
267+
@named model = ODESystem(eqs, t; systems = [ground, body, spring, damping])
268+
269+
sys = structural_simplify(model)
270+
271+
println.(full_equations(sys))
272+
273+
prob = ODEProblem(sys, [], (0, 10.0), [])
274+
sol = solve(prob; dt=0.1, adaptive=false, reltol=1e-9, abstol=1e-9)
275+
276+
return sol
277+
end
278+
nothing # hide
279+
```
280+
281+
Now let's solve the velocity domain model
282+
283+
```@example connections
284+
solv=simplify_and_solve(dv, sv, bv, gv);
285+
nothing # hide
286+
```
287+
288+
And the position domain model
289+
290+
```@example connections
291+
solp=simplify_and_solve(dp, sp, bp, gp);
292+
nothing # hide
293+
```
294+
295+
Now we can plot the comparison of the 2 models and see they give the same result.
296+
```@example connections
297+
plot(ylabel="mass velocity [m/s]")
298+
plot!(solv, idxs=[bv.v])
299+
plot!(solp, idxs=[bp.v])
300+
savefig("mass_velocity.png"); nothing # hide
301+
```
302+
303+
![Mass Velocity Comparison](mass_velocity.png)
304+
305+
306+
But, what if we wanted to plot the mass position? This is easy for the position based domain, we have the state `bp₊s(t)`, but for the velocity based domain we have `sv₊delta_s(t)` which is the spring stretch. To get the absolute position we add the spring natrual length (1m) and the fixed position (1m). As can be seen, we then get the same result.
307+
308+
```@example connections
309+
plot(ylabel="mass position [m]")
310+
plot!(solv, idxs=[sv.delta_s + 1 + 1])
311+
plot!(solp, idxs=[bp.s])
312+
savefig("mass_position.png"); nothing # hide
313+
```
314+
315+
![Mass Position Comparison](mass_position.png)
316+
317+
So in conclusion, the position based domain gives easier access to absolute position information, but requires more initial condition information.
318+
319+
320+
## Acuracy
321+
One may ask then what is the trade off in terms of numerical acuracy? When we look at the simplified equations, we can see that actually both systems solve the same equations. The differential equations of the velocity domain are
322+
323+
```math
324+
\begin{aligned}
325+
m \cdot \dot{v} + d \cdot v + k \cdot \Delta s = 0 \\
326+
\dot{\Delta s} = v
327+
\end{aligned}
328+
```
329+
330+
And for the position domain are
331+
332+
```math
333+
\begin{aligned}
334+
m \cdot \dot{v} + d \cdot v + k \cdot (s - s_{b_0} - l) = 0 \\
335+
\dot{s} = v
336+
\end{aligned}
337+
```
338+
339+
By definition the spring stretch is
340+
341+
```math
342+
\Delta s = s - s_{b_0} - l
343+
```
344+
345+
Which means both systems are actually solving the same exact system. We can plot the numerical difference between the 2 systems and see the result is negligible.
346+
347+
```@example connections
348+
plot(title="numerical difference: vel. vs. pos. domain", xlabel="time [s]", ylabel="solv[bv.v] .- solp[bp.v]")
349+
time = 0:0.1:10
350+
plot!(time, (solv(time)[bv.v] .- solp(time)[bp.v]), label="")
351+
Plots.ylims!(-1e-15, 1e-15)
352+
savefig("err.png"); nothing # hide
353+
```
354+
355+
![Accuracy Comparison](err.png)
356+

docs/src/connectors/model.png

11.7 KB
Loading

0 commit comments

Comments
 (0)