Skip to content

Commit 917ace9

Browse files
authored
Merge pull request JuliaControl#8 from JuliaControl/alloccheck
add static analysis for allocations
2 parents 2f5340a + a37c930 commit 917ace9

File tree

5 files changed

+67
-25
lines changed

5 files changed

+67
-25
lines changed

.github/workflows/CI.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
fail-fast: false
1919
matrix:
2020
version:
21-
- '1.7'
21+
- '1'
2222
os:
2323
- ubuntu-latest
2424
arch:

Project.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ version = "0.1.3"
77
julia = "1.7"
88

99
[extras]
10-
ControlSystems = "a6e380b2-a6ca-5380-bf3e-84a91bcd477e"
10+
AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a"
11+
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
1112
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
1213
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1314

1415
[targets]
15-
test = ["Test", "ControlSystems", "FixedPointNumbers"]
16+
test = ["AllocCheck", "Test", "ControlSystemsBase", "FixedPointNumbers"]

README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ K = 1 # Proportional gain
137137
Ti = 1 # Integral time
138138
Td = 1 # Derivative time
139139
Ts = 0.01 # sample time
140-
P = ss(tf(1, [1, 1])) # Process to be controlled, in continuous time
141-
A,B,C = ssdata(P) # Extract the system matrices
140+
P = ss(tf(1, [1, 1])) # Process to be controlled, in continuous time
141+
A,B,C = ssdata(P) # Extract the system matrices
142142
p = (; A, B, C, r=0, d=1) # reference = 0, disturbance = 1
143143

144144
pid = DiscretePID(; K, Ts, Ti, Td)
@@ -168,14 +168,15 @@ Xm = reduce(hcat, X)' # Reduce to from vector of vectors to matrix
168168
Ym = Xm*P.C' # Compute the output (same as state in this simple case)
169169
Um = reduce(hcat, U)'
170170

171-
plot(t, [Ym Um], layout=(2,1), ylabel = ["y" "u"])
171+
plot(t, [Ym Um], layout=(2,1), ylabel = ["y" "u"], legend=false)
172172
```
173173
Once again, the output looks identical and is omitted here.
174174

175175
## Details
176176
- The derivative term only acts on the (filtered) measurement and not the command signal. It is thus safe to pass step changes in the reference to the controller. The parameter $b$ can further be set to zero to avoid step changes in the control signal in response to step changes in the reference.
177177
- Bumpless transfer when updating `K` is realized by updating the state `I`. See the docs for `set_K!` for more details.
178178
- The total control signal $u(t)$ (PID + feed-forward) is limited by the integral anti-windup.
179+
- When used with input arguments of standard types, such as `Float64` or `Float32`, the controller is guaranteed not to allocate any memory or contain any dynamic dispatches. This analysis is carried out in the tests, and is performed using [AllocCheck.jl](https://github.com/JuliaLang/AllocCheck.jl).
179180

180181
## Simulation of fixed-point arithmetic
181182
If the controller is ultimately to be implemented on a platform without floating-point hardware, you can simulate how it will behave with fixed-point arithmetics using the `FixedPointNumbers` package. The following example modifies the first example above and shows how to simulate the controller using 16-bit fixed-point arithmetics with 10 bits for the fractional part:

src/DiscretePIDs.jl

+28-12
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,38 @@ module DiscretePIDs
22

33
export DiscretePID, calculate_control!, set_K!, set_Td!, set_Ti!
44

5+
"""
6+
DiscretePID{T}
7+
"""
58
mutable struct DiscretePID{T} <: Function
6-
K::T # Proportional gain
7-
Ti::T # Integral time
8-
Td::T # Derivative time
9-
Tt::T # Reset time
10-
N::T # Maximum derivative gain
11-
b::T # Fraction of set point in prop. term
12-
umin::T # Low output limit
13-
umax::T # High output limit
14-
Ts::T # Sampling period
9+
"Proportional gain"
10+
K::T
11+
"Integral time"
12+
Ti::T
13+
"Derivative time"
14+
Td::T
15+
"Reset time"
16+
Tt::T
17+
"Maximum derivative gain"
18+
N::T
19+
"Fraction of set point in prop. term"
20+
b::T
21+
"Low output limit"
22+
umin::T
23+
"High output limit"
24+
umax::T
25+
"Sampling period"
26+
Ts::T
1527
bi::T
1628
ar::T
1729
bd::T
1830
ad::T
19-
I::T # Integral state
20-
D::T # Derivative state
21-
yold::T # Last measurement signal
31+
"Integral state"
32+
I::T
33+
"Derivative state"
34+
D::T
35+
"Last measurement signal"
36+
yold::T
2237
end
2338

2439
"""
@@ -38,6 +53,7 @@ Call the controller like this
3853
```julia
3954
u = pid(r, y, uff) # uff is optional
4055
u = calculate_control!(pid, r, y, uff) # Equivalent to the above
56+
```
4157
4258
# Arguments:
4359
- `K`: Proportional gain

test/runtests.jl

+31-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
using DiscretePIDs
22
using Test
3-
using ControlSystems
3+
using ControlSystemsBase
4+
using AllocCheck
45

56
@testset "DiscretePIDs.jl" begin
7+
8+
# Allocation tests
9+
@testset "allocation" begin
10+
@info "Testing allocation"
11+
for T0 = (Float64, Float32)
12+
for T1 = (Float64, Float32, Int)
13+
for T2 = (Float64, Float32, Int)
14+
for T3 = (Float64, Float32, Int, Nothing)
15+
@test isempty(AllocCheck.check_allocs(calculate_control!, (DiscretePID{T0}, T1, T2)))
16+
@test isempty(AllocCheck.check_allocs(calculate_control!, (DiscretePID{T0}, T1, T2, T3)))
17+
end
18+
end
19+
end
20+
end
21+
end
622

723

824
K = 1
925
Ts = 0.01
1026

1127
pid = DiscretePID(; K, Ts)
1228
display(pid)
29+
for T1 = (Float64, Float32, Int)
30+
for T2 = (Float64, Float32, Int)
31+
for T3 = (Float64, Float32, Int, Nothing)
32+
@test isempty(AllocCheck.check_allocs(pid, (T1, T2)))
33+
@test isempty(AllocCheck.check_allocs(pid, (T1, T2, T3)))
34+
end
35+
end
36+
end
1337

1438
ctrl = function(x,t)
1539
y = (P.C*x)[]
@@ -20,7 +44,7 @@ end
2044
P = c2d(ss(tf(1, [1, 1])), Ts)
2145

2246
## P control
23-
C = c2d(ControlSystems.pid(K, 0), Ts)
47+
C = c2d(ControlSystemsBase.pid(K, 0), Ts)
2448
res = step(feedback(P*C), 3)
2549
res2 = lsim(P, ctrl, 3)
2650

@@ -31,15 +55,15 @@ res2 = lsim(P, ctrl, 3)
3155
## PI control
3256
Ti = 1
3357
pid = DiscretePID(; K, Ts, Ti)
34-
C = c2d(ControlSystems.pid(K, Ti), Ts)
58+
C = c2d(ControlSystemsBase.pid(K, Ti), Ts)
3559
res = step(feedback(P*C), 3)
3660
res2 = lsim(P, ctrl, 3)
3761

3862
# plot([res, res2])
3963
@test res.y res2.y rtol=0.01
4064

4165
## PID control
42-
# Here we simulate a load disturbance instead since the discrete PID does not differentiate r, while the ControlSystems.pid does.
66+
# Here we simulate a load disturbance instead since the discrete PID does not differentiate r, while the ControlSystemsBase.pid does.
4367
Tf = 10
4468
Ti = 1
4569
Td = 1
@@ -89,7 +113,7 @@ ctrl = function(x,t)
89113
u = pid(r, y)
90114
end
91115

92-
C = c2d(ControlSystems.pid(K, Ti), Ts)
116+
C = c2d(ControlSystemsBase.pid(K, Ti), Ts)
93117
res = step(feedback(P*C), Tf)
94118
res2 = lsim(P, ctrl, Tf)
95119

@@ -98,7 +122,7 @@ res2 = lsim(P, ctrl, Tf)
98122

99123

100124
## PID control with bumpless transfer
101-
# Here we simulate a load disturbance instead since the discrete PID does not differentiate r, while the ControlSystems.pid does.
125+
# Here we simulate a load disturbance instead since the discrete PID does not differentiate r, while the ControlSystemsBase.pid does.
102126
Tf = 10
103127
Ti = 1
104128
Td = 1
@@ -138,7 +162,7 @@ ctrl = function(x,t)
138162
r = 1
139163
u = pid(r, y)
140164
end
141-
C = c2d(ControlSystems.pid(K, 0), Ts)
165+
C = c2d(ControlSystemsBase.pid(K, 0), Ts)
142166
res = step(feedback(P*C), 3)
143167
res2 = lsim(P, ctrl, 3)
144168

0 commit comments

Comments
 (0)