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..32b10890e 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,42 @@ 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,7 +299,14 @@ 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") + +function PID(type::Union{AbstractString, Symbol}; kwargs...) + PID(with_I(type), with_D(type); kwargs...) end """ diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index bdd4739c8..6aa0dc750 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,11 @@ 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 plant = Plant() @named fb = Feedback() @named model = ODESystem([ @@ -178,8 +185,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 +194,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 +211,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 +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([ @@ -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),