Skip to content

Commit 3a8fb0a

Browse files
Merge pull request #248 from SciML/bgc/sign_convention
sign convention docs and proof
2 parents e6ffefa + fba3324 commit 3a8fb0a

File tree

3 files changed

+281
-6
lines changed

3 files changed

+281
-6
lines changed

docs/pages.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ pages = [
77
"DC Motor with Speed Controller" => "tutorials/dc_motor_pi.md",
88
"SampledData Component" => "tutorials/input_component.md"
99
],
10-
"About Acausal Connections" => "connectors/connections.md",
10+
"About Acausal Connections" => [
11+
"Theory" => "connectors/connections.md",
12+
"Sign Convention" => "connectors/sign_convention.md",
13+
],
1114
"API" => [
1215
"Basic Blocks" => "API/blocks.md",
1316
"Electrical Components" => "API/electrical.md",
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Sign Convention
2+
3+
A sign convention is recommended for this library that implements the following rule:
4+
5+
> An input component that specifies the *through* variable should be such that an oppossite equality is written such that when connected to a conserved quantity component (i.e. a mass or capacitor component) a positive value for a flow variable represents the accumulation of that conserved quantity over time in the component.
6+
7+
Note: that this implements the same convention as applied in Modelica Standard Library.
8+
9+
For example, the following would be the *correct* sign convention for the `Mechanical.Translational` force variable `f`
10+
11+
```julia
12+
@mtkmodel ConstantForce begin
13+
@parameters begin
14+
f = 0
15+
end
16+
@components begin
17+
flange = MechanicalPort()
18+
end
19+
@equations begin
20+
# connectors
21+
flange.f ~ -f # <-- force is leaving
22+
end
23+
end
24+
```
25+
26+
And writing the following would be the *incorrect* sign convention.
27+
28+
```julia
29+
@equations begin
30+
# connectors
31+
flange.f ~ f # <-- wrong through variable input sign!
32+
end
33+
```
34+
35+
<!-- To visualize the sign convention, one can draw the orientation of the connector port *across* and *through* variables and the subsequent component variables. For example, the `Mechanical.Translation` mass component would look like
36+
37+
![mass](mass.svg)
38+
39+
In this case we know from Newton that mass times acceleration equals force, therefore the direction of movement is in the opposite direction of the force. In other words, if we push the mass from left to right (i.e. in the positive direction), then the mass will generate a force in the negative direction. This would be the general rule for a single port component of any domain. The exception is for a *through* variable **input** component, which should align the component and port connection *through* variables. For example, a force input diagram would look like this:
40+
41+
![force](force_input.svg)
42+
43+
For a 2 port connection component, then the *through* variable is exiting each connection port in opposing directions. Using a damper as an example, if the positive direction is to the right, then the force of the damper is pushing left (i.e. in the negative direction) on connection port `a` and right (positive direction) on connection port `b`.
44+
45+
![damper](damper.svg) -->
46+
# Discussion
47+
48+
The energy dissipation equation that governs the acausal connection definitions should be such that a positive *through* variable input will lead to an increasing *across* variable value.
49+
50+
```math
51+
\frac{\partial \blue across}{\partial t} = \text{ {\green through} input}
52+
```
53+
54+
This is demonstrated in the following domains of `Mechanical`, `Electrical`, and `Hydraulic`.
55+
56+
## Mechanical
57+
58+
The flow variable (i.e. force) input component for the `Mechanical` domain is
59+
60+
```@example sign_convention
61+
using ModelingToolkit
62+
using ModelingToolkitStandardLibrary.Mechanical.Translational
63+
64+
@mtkmodel ConstantForce begin
65+
@parameters begin
66+
f
67+
end
68+
@components begin
69+
flange = MechanicalPort()
70+
end
71+
@equations begin
72+
# connectors
73+
flange.f ~ -f
74+
end
75+
end
76+
```
77+
78+
Here we can see that a positive input force results in an increasing velocity.
79+
80+
```@example sign_convention
81+
@mtkmodel System begin
82+
@components begin
83+
mass = Mass(; m = 10)
84+
force = ConstantForce(; f = 1)
85+
end
86+
@equations begin
87+
connect(mass.flange, force.flange)
88+
end
89+
end
90+
@mtkbuild sys = System()
91+
full_equations(sys)
92+
```
93+
94+
## Electrical
95+
96+
The flow variable (i.e. current) input component for the `Electrical` domain is
97+
98+
```@example sign_convention
99+
using ModelingToolkitStandardLibrary.Electrical
100+
101+
@mtkmodel ConstantCurrent begin
102+
@parameters begin
103+
i
104+
end
105+
@components begin
106+
p = Pin()
107+
n = Pin()
108+
end
109+
@equations begin
110+
0 ~ p.i + n.i
111+
i ~ -n.i # can also be written as i ~ p.i
112+
end
113+
end
114+
```
115+
116+
Here we can see that a positive input current results in an increasing voltage. Note that the electrical domain uses pins `p` and `n` at each side of the source and energy storage components. The direction of connection is not important here, only that a positive connector `p` connects with a negative connector `n`.
117+
118+
```@example sign_convention
119+
@mtkmodel System begin
120+
@components begin
121+
capacitor = Capacitor(; C = 10)
122+
current = ConstantCurrent(; i = 1)
123+
ground = Ground()
124+
end
125+
@equations begin
126+
connect(current.n, capacitor.p)
127+
connect(capacitor.n, current.p, ground.g)
128+
end
129+
end
130+
@mtkbuild sys = System()
131+
full_equations(sys)
132+
```
133+
134+
Reversing the pins gives the same result
135+
136+
```@example sign_convention
137+
@mtkmodel System begin
138+
@components begin
139+
capacitor = Capacitor(; C = 10)
140+
current = ConstantCurrent(; i = 1)
141+
ground = Ground()
142+
end
143+
@equations begin
144+
connect(current.p, capacitor.n)
145+
connect(capacitor.p, current.n, ground.g)
146+
end
147+
end
148+
@mtkbuild sys = System()
149+
full_equations(sys)
150+
```
151+
152+
## Hydraulic
153+
154+
The flow variable (i.e. mass flow) input component for the `Hydraulic` domain is
155+
156+
```@example sign_convention
157+
using ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible
158+
159+
@mtkmodel ConstantMassFlow begin
160+
@parameters begin
161+
p_int
162+
dm
163+
end
164+
@components begin
165+
port = HydraulicPort(; p_int)
166+
end
167+
@equations begin
168+
port.dm ~ -dm
169+
end
170+
end
171+
```
172+
173+
A positive input mass flow leads to an increasing pressure (in this case we get increasing density (`rho`), which is directly related to an increasing pressure).
174+
175+
```@example sign_convention
176+
@mtkmodel System begin
177+
@components begin
178+
volume = FixedVolume(; vol = 10.0, p_int = 0.0)
179+
flow = ConstantMassFlow(; dm = 1)
180+
fluid = HydraulicFluid()
181+
end
182+
@equations begin
183+
connect(flow.port, volume.port)
184+
connect(fluid, flow.port)
185+
end
186+
end
187+
@mtkbuild sys = System()
188+
full_equations(sys) |> first
189+
```

src/Hydraulic/IsothermalCompressible/components.jl

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,89 @@ Fixed fluid volume.
430430
ODESystem(eqs, t, vars, pars; name, systems)
431431
end
432432

433+
"""
434+
Volume(; x, dx=0, p, drho=0, dm=0, area, direction = +1, name)
435+
436+
Volume with moving wall with `flange` connector for converting hydraulic energy to 1D mechanical. The `direction` argument aligns the mechanical port with the hydraulic port, useful when connecting two dynamic volumes together in oppsing directions to create an actuator.
437+
438+
```
439+
┌─────────────────┐ ───
440+
│ │ ▲
441+
│ │
442+
dm ────► │ │ area
443+
│ │
444+
│ │ ▼
445+
└─────────────────┤ ───
446+
447+
└─► x (= ∫ flange.v * direction)
448+
```
449+
450+
# Parameters:
451+
## volume
452+
- `p`: [Pa] initial pressure
453+
- `area`: [m^2] moving wall area
454+
- `x`: [m] initial wall position
455+
- `dx=0`: [m/s] initial wall velocity
456+
- `drho=0`: [kg/m^3/s] initial density derivative
457+
- `dm=0`: [kg/s] initial flow
458+
459+
- `direction`: [+/-1] applies the direction conversion from the `flange` to `x`
460+
461+
# Connectors:
462+
- `port`: hydraulic port
463+
- `flange`: mechanical translational port
464+
465+
See also [`FixedVolume`](@ref), [`DynamicVolume`](@ref)
466+
"""
467+
@component function Volume(;
468+
#initial conditions
469+
x,
470+
dx = 0,
471+
p,
472+
drho = 0,
473+
dm = 0,
474+
475+
#parameters
476+
area,
477+
direction = +1, name)
478+
pars = @parameters begin
479+
area = area
480+
end
481+
482+
vars = @variables begin
483+
x(t) = x
484+
dx(t) = dx
485+
p(t) = p
486+
f(t) = p * area
487+
rho(t)
488+
drho(t) = drho
489+
dm(t) = dm
490+
end
491+
492+
systems = @named begin
493+
port = HydraulicPort(; p_int = p)
494+
flange = MechanicalPort(; f, v = dx)
495+
end
496+
497+
eqs = [
498+
# connectors
499+
port.p ~ p
500+
port.dm ~ dm
501+
flange.v * direction ~ dx
502+
flange.f * direction ~ -f
503+
504+
# differentials
505+
D(x) ~ dx
506+
D(rho) ~ drho
507+
508+
# physics
509+
rho ~ liquid_density(port, p)
510+
f ~ p * area
511+
dm ~ drho * x * area + rho * dx * area]
512+
513+
ODESystem(eqs, t, vars, pars; name, systems, defaults = [rho => liquid_density(port)])
514+
end
515+
433516
"""
434517
DynamicVolume(N, add_inertia=true; p_int, area, x_int = 0, x_max, x_min = 0, x_damp = x_min, direction = +1, perimeter = 2 * sqrt(area * pi), shape_factor = 64, head_factor = 1, Cd = 1e2, Cd_reverse = Cd, name)
435518
@@ -509,7 +592,7 @@ dm ────► │ │ area
509592
x_min = x_min
510593
x_damp = x_damp
511594

512-
direction = direction
595+
# direction = direction
513596

514597
perimeter = perimeter
515598
shape_factor = shape_factor
@@ -588,11 +671,11 @@ dm ────► │ │ area
588671
push!(volumes, comp)
589672
end
590673

591-
push!(eqs, connect(moving_volume.port, volumes[1].port, pipe_bases[1].port_a))
592-
push!(eqs, connect(pipe_bases[end].port_b, damper.port_a))
593-
for i in 2:N
674+
push!(eqs, connect(moving_volume.port, volumes[end].port, pipe_bases[end].port_a))
675+
push!(eqs, connect(pipe_bases[1].port_b, damper.port_a))
676+
for i in 1:(N - 1)
594677
push!(eqs,
595-
connect(volumes[i].port, pipe_bases[i - 1].port_b, pipe_bases[i].port_a))
678+
connect(volumes[i].port, pipe_bases[i + 1].port_b, pipe_bases[i].port_a))
596679
end
597680

598681
for i in 1:N

0 commit comments

Comments
 (0)