Skip to content

sign convention docs and proof #248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/pages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ pages = [
"DC Motor with Speed Controller" => "tutorials/dc_motor_pi.md",
"SampledData Component" => "tutorials/input_component.md"
],
"About Acausal Connections" => "connectors/connections.md",
"About Acausal Connections" => [
"Theory" => "connectors/connections.md",
"Sign Convention" => "connectors/sign_convention.md",
],
"API" => [
"Basic Blocks" => "API/blocks.md",
"Electrical Components" => "API/electrical.md",
Expand Down
189 changes: 189 additions & 0 deletions docs/src/connectors/sign_convention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Sign Convention

A sign convention is recommended for this library that implements the following rule:

> 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.

Note: that this implements the same convention as applied in Modelica Standard Library.

For example, the following would be the *correct* sign convention for the `Mechanical.Translational` force variable `f`

```julia
@mtkmodel ConstantForce begin
@parameters begin
f = 0
end
@components begin
flange = MechanicalPort()
end
@equations begin
# connectors
flange.f ~ -f # <-- force is leaving
end
end
```

And writing the following would be the *incorrect* sign convention.

```julia
@equations begin
# connectors
flange.f ~ f # <-- wrong through variable input sign!
end
```

<!-- 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

![mass](mass.svg)

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:

![force](force_input.svg)

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`.

![damper](damper.svg) -->
# Discussion

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.

```math
\frac{\partial \blue across}{\partial t} = \text{ {\green through} input}
```

This is demonstrated in the following domains of `Mechanical`, `Electrical`, and `Hydraulic`.

## Mechanical

The flow variable (i.e. force) input component for the `Mechanical` domain is

```@example sign_convention
using ModelingToolkit
using ModelingToolkitStandardLibrary.Mechanical.Translational

@mtkmodel ConstantForce begin
@parameters begin
f
end
@components begin
flange = MechanicalPort()
end
@equations begin
# connectors
flange.f ~ -f
end
end
```

Here we can see that a positive input force results in an increasing velocity.

```@example sign_convention
@mtkmodel System begin
@components begin
mass = Mass(; m = 10)
force = ConstantForce(; f = 1)
end
@equations begin
connect(mass.flange, force.flange)
end
end
@mtkbuild sys = System()
full_equations(sys)
```

## Electrical

The flow variable (i.e. current) input component for the `Electrical` domain is

```@example sign_convention
using ModelingToolkitStandardLibrary.Electrical

@mtkmodel ConstantCurrent begin
@parameters begin
i
end
@components begin
p = Pin()
n = Pin()
end
@equations begin
0 ~ p.i + n.i
i ~ -n.i # can also be written as i ~ p.i
end
end
```

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`.

```@example sign_convention
@mtkmodel System begin
@components begin
capacitor = Capacitor(; C = 10)
current = ConstantCurrent(; i = 1)
ground = Ground()
end
@equations begin
connect(current.n, capacitor.p)
connect(capacitor.n, current.p, ground.g)
end
end
@mtkbuild sys = System()
full_equations(sys)
```

Reversing the pins gives the same result

```@example sign_convention
@mtkmodel System begin
@components begin
capacitor = Capacitor(; C = 10)
current = ConstantCurrent(; i = 1)
ground = Ground()
end
@equations begin
connect(current.p, capacitor.n)
connect(capacitor.p, current.n, ground.g)
end
end
@mtkbuild sys = System()
full_equations(sys)
```

## Hydraulic

The flow variable (i.e. mass flow) input component for the `Hydraulic` domain is

```@example sign_convention
using ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible

@mtkmodel ConstantMassFlow begin
@parameters begin
p_int
dm
end
@components begin
port = HydraulicPort(; p_int)
end
@equations begin
port.dm ~ -dm
end
end
```

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).

```@example sign_convention
@mtkmodel System begin
@components begin
volume = FixedVolume(; vol = 10.0, p_int = 0.0)
flow = ConstantMassFlow(; dm = 1)
fluid = HydraulicFluid()
end
@equations begin
connect(flow.port, volume.port)
connect(fluid, flow.port)
end
end
@mtkbuild sys = System()
full_equations(sys) |> first
```
93 changes: 88 additions & 5 deletions src/Hydraulic/IsothermalCompressible/components.jl
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,89 @@ Fixed fluid volume.
ODESystem(eqs, t, vars, pars; name, systems)
end

"""
Volume(; x, dx=0, p, drho=0, dm=0, area, direction = +1, name)

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.

```
┌─────────────────┐ ───
│ │ ▲
│ │
dm ────► │ │ area
│ │
│ │ ▼
└─────────────────┤ ───
└─► x (= ∫ flange.v * direction)
```

# Parameters:
## volume
- `p`: [Pa] initial pressure
- `area`: [m^2] moving wall area
- `x`: [m] initial wall position
- `dx=0`: [m/s] initial wall velocity
- `drho=0`: [kg/m^3/s] initial density derivative
- `dm=0`: [kg/s] initial flow

- `direction`: [+/-1] applies the direction conversion from the `flange` to `x`

# Connectors:
- `port`: hydraulic port
- `flange`: mechanical translational port

See also [`FixedVolume`](@ref), [`DynamicVolume`](@ref)
"""
@component function Volume(;
#initial conditions
x,
dx = 0,
p,
drho = 0,
dm = 0,

#parameters
area,
direction = +1, name)
pars = @parameters begin
area = area
end

vars = @variables begin
x(t) = x
dx(t) = dx
p(t) = p
f(t) = p * area
rho(t)
drho(t) = drho
dm(t) = dm
end

systems = @named begin
port = HydraulicPort(; p_int = p)
flange = MechanicalPort(; f, v = dx)
end

eqs = [
# connectors
port.p ~ p
port.dm ~ dm
flange.v * direction ~ dx
flange.f * direction ~ -f

# differentials
D(x) ~ dx
D(rho) ~ drho

# physics
rho ~ liquid_density(port, p)
f ~ p * area
dm ~ drho * x * area + rho * dx * area]

ODESystem(eqs, t, vars, pars; name, systems, defaults = [rho => liquid_density(port)])
end

"""
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)

Expand Down Expand Up @@ -509,7 +592,7 @@ dm ────► │ │ area
x_min = x_min
x_damp = x_damp

direction = direction
# direction = direction

perimeter = perimeter
shape_factor = shape_factor
Expand Down Expand Up @@ -588,11 +671,11 @@ dm ────► │ │ area
push!(volumes, comp)
end

push!(eqs, connect(moving_volume.port, volumes[1].port, pipe_bases[1].port_a))
push!(eqs, connect(pipe_bases[end].port_b, damper.port_a))
for i in 2:N
push!(eqs, connect(moving_volume.port, volumes[end].port, pipe_bases[end].port_a))
push!(eqs, connect(pipe_bases[1].port_b, damper.port_a))
for i in 1:(N - 1)
push!(eqs,
connect(volumes[i].port, pipe_bases[i - 1].port_b, pipe_bases[i].port_a))
connect(volumes[i].port, pipe_bases[i + 1].port_b, pipe_bases[i].port_a))
end

for i in 1:N
Expand Down