From 4cdc9d9ae874088d486db89d0c0c873d1745a5b5 Mon Sep 17 00:00:00 2001 From: Brad Carman Date: Tue, 3 Oct 2023 19:35:03 -0400 Subject: [PATCH 1/3] PID block fix for symbolic parameters --- src/Blocks/Blocks.jl | 2 +- src/Blocks/continuous.jl | 52 ++++++++++++++++++++++++++------------- test/Blocks/continuous.jl | 40 +++++++++++++++++------------- test/runtests.jl | 7 ++++++ 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index dbf067e3f..fd8d74c5d 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -26,7 +26,7 @@ export Limiter, DeadZone, SlewRateLimiter include("nonlinear.jl") export Integrator, Derivative, FirstOrder, SecondOrder, StateSpace -export PI, LimPI, PID, LimPID +export PI, LimPI, PID, PD, LimPID include("continuous.jl") export AnalysisPoint, get_sensitivity, get_comp_sensitivity, diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 852120189..5327febf4 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -214,15 +214,15 @@ See also [`LimPI`](@ref) end """ - PID(;name, k=1, Ti=false, Td=false, Nd=10, int__x=0, der__x=0) + PID(with_I = true, with_D = true; name, k=1, Ti=0.1, Td=0.1, Nd=10, int__x=0, der__x=0) Text-book version of a PID-controller without actuator saturation and anti-windup measure. # Parameters: - `k`: Gain - - `Ti`: [s] Integrator time constant (Ti>0 required). If set to false, no integral action is used. - - `Td`: [s] Derivative time constant (Td>0 required). If set to false, no derivative action is used. + - `Ti`: [s] Integrator time constant (Ti>0 required). If `with_I` set to false, no integral action is used. + - `Td`: [s] Derivative time constant (Td>0 required). If `with_D` set to false, no derivative action is used. - `Nd`: [s] Time constant for the derivative approximation (Nd>0 required; Nd=0 is ideal derivative). - `int__x`: Initial value for the integrator. - `der__x`: Initial value for the derivative state. @@ -234,29 +234,40 @@ Text-book version of a PID-controller without actuator saturation and anti-windu See also [`LimPID`](@ref) """ -@component function PID(; name, k = 1, Ti = false, Td = false, Nd = 10, int__x = 0, +@component function PID(with_I = true, with_D = true; name, k = 1, Ti = 0.1, Td = 0.1, Nd = 10, int__x = 0, der__x = 0) - with_I = !isequal(Ti, false) - with_D = !isequal(Td, false) + + pars = @parameters begin + k = k + Ti = Ti + Td = Td + Nd = Nd + int__x = int__x + der__x = der__x + end + @named err_input = RealInput() # control error @named ctr_output = RealOutput() # control signal - !isequal(Ti, false) && - (Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0"))) - !isequal(Td, false) && - (Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0"))) - Nd > 0 || throw(ArgumentError("Nd out of bounds, got $(Nd) but expected Nd > 0")) - @named gainPID = Gain(k) + with_I && + (@symcheck Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0"))) + with_D && + (@symcheck Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0"))) + + @symcheck Nd > 0 || + throw(ArgumentError("Nd out of bounds, got $(Nd) but expected Nd ≥ 0")) + + @named gainPID = Gain(; k) @named addPID = Add3() if with_I - @named int = Integrator(k = 1 / Ti, x = int__x) + @named int = Integrator(;k = 1 / Ti, x = int__x) else - @named Izero = Constant(k = 0) + @named Izero = Constant(;k = 0) end if with_D - @named der = Derivative(k = Td, T = 1 / Nd, x = der__x) + @named der = Derivative(;k = Td, T = 1 / Nd, x = der__x) else - @named Dzero = Constant(k = 0) + @named Dzero = Constant(;k = 0) end sys = [err_input, ctr_output, gainPID, addPID] if with_I @@ -286,9 +297,16 @@ See also [`LimPID`](@ref) else push!(eqs, connect(Dzero.output, addPID.input3)) end - ODESystem(eqs, t, [], []; name = name, systems = sys) + ODESystem(eqs, t, [], pars; name = name, systems = sys) end +with_I(type::Union{AbstractString, Symbol}) = contains(lowercase(string(type)), "i") +with_D(type::Union{AbstractString, Symbol}) = contains(lowercase(string(type)), "d") + +PID(type::Union{AbstractString, Symbol}; kwargs...) = PID(with_I(type), with_D(type); kwargs...) + + + """ LimPI(; name, k = 1.0, T, Ta, int__x = 0.0, u_max = 1.0, u_min = -u_max) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index bdd4739c8..a2276e654 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -1,3 +1,4 @@ +using Test using ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq using ModelingToolkitStandardLibrary.Blocks using OrdinaryDiffEq: ReturnCode.Success @@ -12,9 +13,13 @@ an integrator with a constant input is often used together with the system under =# @testset "Constant" begin - @named c = Constant(; k = 1) - @named int = Integrator(x = 1) - @named iosys = ODESystem(connect(c.output, int.input), t, systems = [int, c]) + pars = @parameters begin + k = 1 + x = 1 + end + @named c = Constant(; k) + @named int = Integrator(; x) + @named iosys = ODESystem(connect(c.output, int.input), t, [], pars; systems = [int, c]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[], (0.0, 1.0)) sol = solve(prob, Rodas4()) @@ -167,9 +172,12 @@ end end @testset "PID" begin + + @parameters Ti=0.5 Td=1/100 + @named pid_controller = PID(; k = 3, Ti, Td) + re_val = 2 - @named ref = Constant(; k = re_val) - @named pid_controller = PID(k = 3, Ti = 0.5, Td = 1 / 100) + @named ref = Constant(; k = re_val) @named plant = Plant() @named fb = Feedback() @named model = ODESystem([ @@ -178,8 +186,7 @@ end connect(fb.output, pid_controller.err_input), connect(pid_controller.ctr_output, plant.input), ], - t, - systems = [pid_controller, plant, ref, fb]) + t, [], [Ti, Td]; systems = [pid_controller, plant, ref, fb]) sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) @@ -188,15 +195,14 @@ end @test sol[plant.output.u][end]≈re_val atol=1e-3 # zero control error after 100s @testset "PI" begin - @named pid_controller = PID(k = 3, Ti = 0.5, Td = false) + @named pid_controller = PID("PI"; k = 3, Ti) @named model = ODESystem([ connect(ref.output, fb.input1), connect(plant.output, fb.input2), connect(fb.output, pid_controller.err_input), connect(pid_controller.ctr_output, plant.input), ], - t, - systems = [pid_controller, plant, ref, fb]) + t, [], [Ti]; systems = [pid_controller, plant, ref, fb]) sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) @@ -206,7 +212,7 @@ end end @testset "PD" begin - @named pid_controller = PID(k = 10, Ti = false, Td = 1) + @named pid_controller = PID("PD"; k = 10, Td = 1) @named model = ODESystem([ connect(ref.output, fb.input1), connect(plant.output, fb.input2), @@ -284,7 +290,7 @@ end @testset "LimPID" begin re_val = 1 @named ref = Constant(; k = re_val) - @named pid_controller = LimPID(k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, u_min = -1.5, + @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, u_min = -1.5, Ni = 0.1 / 0.5) @named plant = Plant() @named model = ODESystem([ @@ -305,7 +311,7 @@ end @test all(-1.5 .<= sol[pid_controller.ctr_output.u] .<= 1.5) # test limit @testset "PI" begin - @named pid_controller = LimPID(k = 3, Ti = 0.5, Td = false, u_max = 1.5, + @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = false, u_max = 1.5, u_min = -1.5, Ni = 0.1 / 0.5) @named model = ODESystem([ connect(ref.output, pid_controller.reference), @@ -325,7 +331,7 @@ end @test all(-1.5 .<= sol[pid_controller.ctr_output.u] .<= 1.5) # test limit end @testset "PD" begin - @named pid_controller = LimPID(k = 10, Ti = false, Td = 1, u_max = 1.5, + @named pid_controller = LimPID(; k = 10, Ti = false, Td = 1, u_max = 1.5, u_min = -1.5) @named model = ODESystem([ connect(ref.output, pid_controller.reference), @@ -346,7 +352,7 @@ end end @testset "set-point weights" begin @testset "wp" begin - @named pid_controller = LimPID(k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, + @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, u_min = -1.5, Ni = 0.1 / 0.5, wp = 0, wd = 1) @named model = ODESystem([ connect(ref.output, pid_controller.reference), @@ -367,7 +373,7 @@ end @test all(-1.5 .<= sol[pid_controller.ctr_output.u] .<= 1.5) # test limit end @testset "wd" begin - @named pid_controller = LimPID(k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, + @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, u_min = -1.5, Ni = 0.1 / 0.5, wp = 1, wd = 0) @named model = ODESystem([ connect(ref.output, pid_controller.reference), @@ -389,7 +395,7 @@ end end end @testset "PI without AWM" begin - @named pid_controller = LimPID(k = 3, Ti = 0.5, Td = false, u_max = 1.5, + @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = false, u_max = 1.5, u_min = -1.5, Ni = Inf) @named model = ODESystem([ connect(ref.output, pid_controller.reference), diff --git a/test/runtests.jl b/test/runtests.jl index 178984894..1de7e0da4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,11 @@ using SafeTestsets +using ModelingToolkitStandardLibrary + +# ModelingToolkitStandardLibrary +# symcheck +@parameters x = 1 +@symcheck x > 0 + # Blocks @safetestset "Blocks: math" begin From 87157cde0dee842384f5bb3e57a69cd285aa0f83 Mon Sep 17 00:00:00 2001 From: Brad Carman Date: Tue, 3 Oct 2023 19:35:30 -0400 Subject: [PATCH 2/3] format --- src/Blocks/continuous.jl | 26 ++++++++++++++------------ test/Blocks/continuous.jl | 10 +++++----- test/runtests.jl | 1 - 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 5327febf4..32b10890e 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -234,9 +234,9 @@ Text-book version of a PID-controller without actuator saturation and anti-windu See also [`LimPID`](@ref) """ -@component function PID(with_I = true, with_D = true; name, k = 1, Ti = 0.1, Td = 0.1, Nd = 10, int__x = 0, +@component function PID(with_I = true, with_D = true; name, k = 1, Ti = 0.1, Td = 0.1, + Nd = 10, int__x = 0, der__x = 0) - pars = @parameters begin k = k Ti = Ti @@ -250,24 +250,26 @@ See also [`LimPID`](@ref) @named ctr_output = RealOutput() # control signal with_I && - (@symcheck Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0"))) + (@symcheck Ti ≥ 0 || + throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0"))) with_D && - (@symcheck Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0"))) + (@symcheck Td ≥ 0 || + throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0"))) @symcheck Nd > 0 || - throw(ArgumentError("Nd out of bounds, got $(Nd) but expected Nd ≥ 0")) + throw(ArgumentError("Nd out of bounds, got $(Nd) but expected Nd ≥ 0")) @named gainPID = Gain(; k) @named addPID = Add3() if with_I - @named int = Integrator(;k = 1 / Ti, x = int__x) + @named int = Integrator(; k = 1 / Ti, x = int__x) else - @named Izero = Constant(;k = 0) + @named Izero = Constant(; k = 0) end if with_D - @named der = Derivative(;k = Td, T = 1 / Nd, x = der__x) + @named der = Derivative(; k = Td, T = 1 / Nd, x = der__x) else - @named Dzero = Constant(;k = 0) + @named Dzero = Constant(; k = 0) end sys = [err_input, ctr_output, gainPID, addPID] if with_I @@ -303,9 +305,9 @@ end with_I(type::Union{AbstractString, Symbol}) = contains(lowercase(string(type)), "i") with_D(type::Union{AbstractString, Symbol}) = contains(lowercase(string(type)), "d") -PID(type::Union{AbstractString, Symbol}; kwargs...) = PID(with_I(type), with_D(type); kwargs...) - - +function PID(type::Union{AbstractString, Symbol}; kwargs...) + PID(with_I(type), with_D(type); kwargs...) +end """ LimPI(; name, k = 1.0, T, Ta, int__x = 0.0, u_max = 1.0, u_min = -u_max) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index a2276e654..6aa0dc750 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -172,12 +172,11 @@ end end @testset "PID" begin - - @parameters Ti=0.5 Td=1/100 + @parameters Ti=0.5 Td=1 / 100 @named pid_controller = PID(; k = 3, Ti, Td) re_val = 2 - @named ref = Constant(; k = re_val) + @named ref = Constant(; k = re_val) @named plant = Plant() @named fb = Feedback() @named model = ODESystem([ @@ -290,7 +289,8 @@ end @testset "LimPID" begin re_val = 1 @named ref = Constant(; k = re_val) - @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, u_min = -1.5, + @named pid_controller = LimPID(; k = 3, Ti = 0.5, Td = 1 / 100, u_max = 1.5, + u_min = -1.5, Ni = 0.1 / 0.5) @named plant = Plant() @named model = ODESystem([ @@ -331,7 +331,7 @@ end @test all(-1.5 .<= sol[pid_controller.ctr_output.u] .<= 1.5) # test limit end @testset "PD" begin - @named pid_controller = LimPID(; k = 10, Ti = false, Td = 1, u_max = 1.5, + @named pid_controller = LimPID(; k = 10, Ti = false, Td = 1, u_max = 1.5, u_min = -1.5) @named model = ODESystem([ connect(ref.output, pid_controller.reference), diff --git a/test/runtests.jl b/test/runtests.jl index 1de7e0da4..d1ef969bc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,7 +6,6 @@ using ModelingToolkitStandardLibrary @parameters x = 1 @symcheck x > 0 - # Blocks @safetestset "Blocks: math" begin include("Blocks/math.jl") From 1aaef71f931ab9debe8d89499792495485801b9e Mon Sep 17 00:00:00 2001 From: Bradley Carman <40798837+bradcarman@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:55:35 -0400 Subject: [PATCH 3/3] Update runtests.jl --- test/runtests.jl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index d1ef969bc..178984894 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,10 +1,4 @@ using SafeTestsets -using ModelingToolkitStandardLibrary - -# ModelingToolkitStandardLibrary -# symcheck -@parameters x = 1 -@symcheck x > 0 # Blocks @safetestset "Blocks: math" begin