Skip to content

Commit 78d0f86

Browse files
Merge branch 'SciML:main' into ag/planar-mechanics
2 parents f454f1b + c85424e commit 78d0f86

File tree

9 files changed

+199
-31
lines changed

9 files changed

+199
-31
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelingToolkitStandardLibrary"
22
uuid = "16a59e39-deab-5bd0-87e4-056b12336739"
33
authors = ["Chris Rackauckas and Julia Computing"]
4-
version = "2.3.3"
4+
version = "2.3.4"
55

66
[deps]
77
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"

docs/src/API/blocks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Derivative
8080
FirstOrder
8181
SecondOrder
8282
StateSpace
83+
TransferFunction
8384
PI
8485
LimPI
8586
PID

src/Blocks/Blocks.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ include("sources.jl")
2525
export Limiter, DeadZone, SlewRateLimiter
2626
include("nonlinear.jl")
2727

28-
export Integrator, Derivative, FirstOrder, SecondOrder, StateSpace
28+
export Integrator, Derivative, FirstOrder, SecondOrder, StateSpace, TransferFunction
2929
export PI, LimPI, PID, LimPID
3030
include("continuous.jl")
3131

src/Blocks/continuous.jl

Lines changed: 121 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""
22
Integrator(;name, k = 1, x = 0.0)
33
4-
Outputs `y = ∫k*u dt`, corresponding to the transfer function `1/s`.
5-
Initial value of integrator state `x` can be set with `x`
4+
Outputs `y = ∫k*u dt`, corresponding to the transfer function ``1/s``.
5+
Initial value of integrator state ``x`` can be set with `x`
66
77
# Connectors:
88
@@ -19,7 +19,7 @@ Initial value of integrator state `x` can be set with `x`
1919
x(t) = 0.0, [description = "State of Integrator"]
2020
end
2121
@parameters begin
22-
k = 1, [description = "Gain of Integrator"]
22+
k = 1, [description = "Gain"]
2323
end
2424
@equations begin
2525
D(x) ~ k * u
@@ -30,7 +30,7 @@ end
3030
Derivative(; name, k = 1, T, x = 0.0)
3131
3232
Outputs an approximate derivative of the input. The transfer function of this block is
33-
Initial value of the state `x` can be set with `x`
33+
Initial value of the state ``x`` can be set with `x`
3434
3535
```
3636
k k
@@ -57,11 +57,11 @@ A smaller `T` leads to a more ideal approximation of the derivative.
5757
@mtkmodel Derivative begin
5858
@extend u, y = siso = SISO()
5959
@variables begin
60-
x(t) = 0.0, [description = "State of Derivative"]
60+
x(t) = 0.0, [description = "Derivative-filter state"]
6161
end
6262
@parameters begin
63-
T = T, [description = "Time constant of Derivative"]
64-
k = 1, [description = "Gain of Derivative"]
63+
T = T, [description = "Time constant"]
64+
k = 1, [description = "Gain"]
6565
end
6666
begin
6767
@symcheck T > 0 ||
@@ -77,8 +77,8 @@ end
7777
FirstOrder(; name, k = 1.0, T, x = 0.0, lowpass = true)
7878
7979
A first-order filter with a single real pole in `s = -T` and gain `k`. If `lowpass=true` (default), the transfer function
80-
is given by `Y(s)/U(s) = `
81-
Initial value of the state `x` can be set with `x`
80+
is given by ``Y(s)/U(s) = ``
81+
8282
8383
```
8484
k
@@ -94,10 +94,12 @@ sT + 1 - k
9494
sT + 1
9595
```
9696
97+
Initial value of the state `x` can be set with `x`
98+
9799
# Parameters:
98100
99101
- `k`: Gain
100-
- `T`: [s] Time constants (T>0 required)
102+
- `T`: [s] Time constant (T>0 required)
101103
102104
# Connectors:
103105
@@ -108,26 +110,28 @@ See also [`SecondOrder`](@ref)
108110
"""
109111
@mtkmodel FirstOrder begin
110112
@extend u, y = siso = SISO()
113+
@structural_parameters begin
114+
lowpass = true
115+
end
111116
@variables begin
112117
x(t) = 0.0, [description = "State of FirstOrder filter"]
113118
end
114119
@parameters begin
115-
lowpass = true
116-
T = T, [description = "Time constant of FirstOrder filter"]
117-
k = 1.0, [description = "Gain of FirstOrder"]
120+
T = T, [description = "Time constant"]
121+
k = 1.0, [description = "Gain"]
118122
end
119123
begin
120124
@symcheck T > 0 ||
121125
throw(ArgumentError("Time constant `T` has to be strictly positive"))
122126
end
123127
@equations begin
124128
D(x) ~ (k * u - x) / T
125-
getdefault(lowpass) ? y ~ x : y ~ k * u - x
129+
lowpass ? y ~ x : y ~ k * u - x
126130
end
127131
end
128132

129133
"""
130-
SecondOrder(; name, k = 1.0, w, d, x = 0.0, xd = 0.0)
134+
SecondOrder(; name, k = 1.0, w = 1.0, d = 1.0, x = 0.0, xd = 0.0)
131135
132136
A second-order filter with gain `k`, a bandwidth of `w` rad/s and relative damping `d`. The transfer function
133137
is given by `Y(s)/U(s) = `
@@ -160,9 +164,9 @@ Initial value of the state `x` can be set with `x`, and of derivative state `xd`
160164
xd(t) = 0.0, [description = "Derivative state of SecondOrder filter"]
161165
end
162166
@parameters begin
163-
k = 1.0, [description = "Gain of SecondOrder"]
164-
w, [description = "Bandwidth of SecondOrder"]
165-
d, [description = "Relative damping of SecondOrder"]
167+
k = 1.0, [description = "Gain"]
168+
w = 1.0, [description = "Bandwidth (angular frequency)"]
169+
d = 1.0, [description = "Relative damping"]
166170
end
167171
@equations begin
168172
D(x) ~ xd
@@ -172,14 +176,19 @@ Initial value of the state `x` can be set with `x`, and of derivative state `xd`
172176
end
173177

174178
"""
175-
PI(;name, gainPI.k = 1.0, T, int.x = 0.0)
179+
PI(;name, k = 1.0, T = 1.0, int.x = 0.0)
176180
177181
Textbook version of a PI-controller without actuator saturation and anti-windup measure.
178-
The proportional gain can be set with `gainPI.k`
182+
The proportional gain can be set with `k`
179183
Initial value of integrator state `x` can be set with `int.x`
180184
181-
# Parameters:
185+
The PI controller is implemented on standard form:
186+
```math
187+
U(s) = k (1 + \\dfrac{1}{sT}) E(S)
188+
```
182189
190+
# Parameters:
191+
- `k`: Proportional gain
183192
- `T`: [s] Integrator time constant (T>0 required)
184193
185194
# Connectors:
@@ -191,7 +200,8 @@ See also [`LimPI`](@ref)
191200
"""
192201
@mtkmodel PI begin
193202
@parameters begin
194-
T, [description = "Integrator time constant"]
203+
k = 1.0, [description = "Proportional gain"]
204+
T = 1.0, [description = "Integrator time constant"]
195205
end
196206
begin
197207
@symcheck T > 0 ||
@@ -200,7 +210,7 @@ See also [`LimPI`](@ref)
200210
@components begin
201211
err_input = RealInput() # control error
202212
ctr_output = RealOutput() # control signal
203-
gainPI = Gain(; k = 1.0)
213+
gainPI = Gain(; k)
204214
addPI = Add()
205215
int = Integrator(k = 1 / T, x = 0.0)
206216
end
@@ -294,6 +304,12 @@ end
294304
295305
Text-book version of a PI-controller with actuator saturation and anti-windup measure.
296306
307+
The PI controller is implemented on standard form
308+
```math
309+
u(t) = sat(k (e(t) + ∫\\dfrac{1}{T}e(t) dt) )
310+
```
311+
The simplified expression above is given without the anti-windup protection.
312+
297313
# Parameters:
298314
299315
- `k`: Proportional gain
@@ -537,3 +553,85 @@ linearized around the operating point `x₀, u₀`, we have `y0, u0 = h(x₀, u
537553
end
538554

539555
StateSpace(A, B, C, D = nothing; kwargs...) = StateSpace(; A, B, C, D, kwargs...)
556+
557+
symbolic_eps(t) = eps(t)
558+
@register_symbolic symbolic_eps(t)
559+
560+
"""
561+
TransferFunction(; b, a, name)
562+
563+
A single input, single output, linear time-invariant system provided as a transfer-function.
564+
```
565+
Y(s) = b(s) / a(s) U(s)
566+
```
567+
where `b` and `a` are vectors of coefficients of the numerator and denominator polynomials, respectively, ordered such that the coefficient of the highest power of `s` is first.
568+
569+
The internal state realization is on controller canonical form, with state variable `x`, output variable `y` and input variable `u`. For numerical robustness, the realization used by the integrator is scaled by the last entry of the `a` parameter. The internally scaled state variable is available as `x_scaled`.
570+
571+
To set the initial state, it's recommended to set the initial condition for `x`, and let that of `x_scaled` be computed automatically.
572+
573+
# Parameters:
574+
- `b`: Numerator polynomial coefficients, e.g., `2s + 3` is specified as `[2, 3]`
575+
- `a`: Denomenator polynomial coefficients, e.g., `s² + 2ωs + ω^2` is specified as `[1, 2ω, ω^2]`
576+
577+
# Connectors:
578+
- `input`
579+
- `output`
580+
581+
See also [`StateSpace`](@ref) which handles MIMO systems, as well as [ControlSystemsMTK.jl](https://juliacontrol.github.io/ControlSystemsMTK.jl/stable/) for an interface between [ControlSystems.jl](https://juliacontrol.github.io/ControlSystems.jl/stable/) and ModelingToolkit.jl for advanced manipulation of transfer functions and linear statespace systems. For linearization, see [`linearize`](@ref) and [Linear Analysis](https://docs.sciml.ai/ModelingToolkitStandardLibrary/stable/API/linear_analysis/).
582+
"""
583+
@component function TransferFunction(; b = [1], a = [1, 1], name)
584+
nb = length(b)
585+
na = length(a)
586+
nb <= na ||
587+
error("Transfer function is not proper, the numerator must not be longer than the denominator")
588+
nx = na - 1
589+
nbb = max(0, na - nb)
590+
591+
@named begin
592+
input = RealInput()
593+
output = RealOutput()
594+
end
595+
596+
@parameters begin
597+
b[1:nb] = b,
598+
[
599+
description = "Numerator coefficients of transfer function (e.g., 2s + 3 is specified as [2,3])",
600+
]
601+
a[1:na] = a,
602+
[
603+
description = "Denominator coefficients of transfer function (e.g., `s² + 2ωs + ω^2` is specified as [1, 2ω, ω^2])",
604+
]
605+
bb[1:(nbb + nb)] = [zeros(nbb); b]
606+
d = bb[1] / a[1], [description = "Direct feedthrough gain"]
607+
end
608+
609+
a = collect(a)
610+
@parameters a_end = ifelse(a[end] > 100 * symbolic_eps(sqrt(a' * a)), a[end], 1.0)
611+
612+
pars = [collect(b); a; collect(bb); d; a_end]
613+
@variables begin
614+
x(t)[1:nx] = zeros(nx),
615+
[description = "State of transfer function on controller canonical form"]
616+
x_scaled(t)[1:nx] = collect(x) * a_end, [description = "Scaled vector x"]
617+
u(t), [description = "Input of transfer function"]
618+
y(t), [description = "Output of transfer function"]
619+
end
620+
621+
x = collect(x)
622+
x_scaled = collect(x_scaled)
623+
624+
sts = [x; x_scaled; y; u]
625+
626+
if nx == 0
627+
eqs = [y ~ d * u]
628+
else
629+
eqs = [D(x_scaled[1]) ~ (-a[2:na]'x_scaled + a_end * u) / a[1]
630+
D.(x_scaled[2:nx]) .~ x_scaled[1:(nx - 1)]
631+
y ~ ((bb[2:na] - d * a[2:na])'x_scaled) / a_end + d * u
632+
x .~ x_scaled ./ a_end]
633+
end
634+
push!(eqs, input.u ~ u)
635+
push!(eqs, output.u ~ y)
636+
compose(ODESystem(eqs, t, sts, pars; name = name), input, output)
637+
end

src/Blocks/nonlinear.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ Limit the range of a signal.
1818
"""
1919
@component function Limiter(; name, y_max, y_min = y_max > 0 ? -y_max : -Inf)
2020
@symcheck y_max y_min || throw(ArgumentError("`y_min` must be smaller than `y_max`"))
21-
@named siso = SISO()
21+
m = (y_max + y_min) / 2
22+
@named siso = SISO(u_start = m, y_start = m) # Default signals to center of saturation to minimize risk of saturation while linearizing etc.
2223
@unpack u, y = siso
2324
pars = @parameters y_max=y_max [description = "Maximum allowed output of Limiter $name"] y_min=y_min [
2425
description = "Minimum allowed output of Limiter $name",

src/Blocks/utils.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ Single input single output (SISO) continuous system block.
7474
y(t) = y_start, [description = "Output of SISO system"]
7575
end
7676
@components begin
77-
input = RealInput(u_start = 0.0)
78-
output = RealOutput(u_start = 0.0)
77+
input = RealInput(u_start = u_start)
78+
output = RealOutput(u_start = y_start)
7979
end
8080
@equations begin
8181
u ~ input.u

src/Hydraulic/IsothermalCompressible/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ end
119119
Symbolics.derivative(::typeof(friction_factor), args, ::Val{1}) = 0
120120
Symbolics.derivative(::typeof(friction_factor), args, ::Val{4}) = 0
121121
function ChainRulesCore.frule(_, ::typeof(friction_factor), args...)
122-
(friction_factor(args...), ChainRulesCore.ZeroTangent)
122+
(friction_factor(args...), ChainRulesCore.ZeroTangent())
123123
end
124124

125125
function transition(x1, x2, y1, y2, x)

0 commit comments

Comments
 (0)