-
Notifications
You must be signed in to change notification settings - Fork 85
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
Convert pid parameters to :parallel #900
Merged
baggepinnen
merged 8 commits into
JuliaControl:master
from
mzaffalon:zaf/pid_parallel_form
Nov 17, 2023
Merged
Changes from 2 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a3b77eb
Convert pid parameters to :parallel
27a0c18
Throw DomainError when conversion to other form is not possible
a43c256
Reintroduce convert_pidparams_from_standard
692b5f9
Re-add broadcasting
c48eda3
Update lib/ControlSystemsBase/src/pid_design.jl
mzaffalon 9908eaf
Remove broadcasting on convert_pidparams* functions
5392ea1
merge
0ac6a75
Update lib/ControlSystemsBase/test/test_pid_design.jl
mzaffalon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,43 +48,41 @@ | |
@deprecate pid(; kp=0, ki=0, kd=0, series = false) pid(kp, ki, kd; form=series ? :series : :parallel) | ||
|
||
function pid_tf(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing) | ||
Kp, Ti, Td = convert_pidparams_to_standard(param_p, param_i, param_d, form) | ||
ia = Ti != Inf && Ti != 0 # integral action, 0 would result in division by zero, but typically indicates that the user wants no integral action | ||
Kp, Ki, Kd = convert_pidparams_to_parallel(param_p, param_i, param_d, form) | ||
if isnothing(Tf) | ||
if ia | ||
return tf([Kp * Td, Kp, Kp / Ti], [1, 0]) | ||
if Ki != 0 | ||
return tf([Kd, Kp, Ki], [1, 0]) | ||
else | ||
return tf([Kp * Td, Kp], [1]) | ||
return tf([Kd, Kp], [1]) | ||
end | ||
else | ||
if ia | ||
return tf([Kp * Td, Kp, Kp / Ti], [Tf^2/2, Tf, 1, 0]) | ||
if Ki != 0 | ||
return tf([Kd, Kp, Ki], [Tf^2/2, Tf, 1, 0]) | ||
else | ||
return tf([Kp * Td, Kp], [Tf^2/2, Tf, 1]) | ||
return tf([Kd, Kp], [Tf^2/2, Tf, 1]) | ||
end | ||
end | ||
end | ||
|
||
function pid_ss(param_p, param_i, param_d=zero(typeof(param_p)); form=:standard, Tf=nothing) | ||
Kp, Ti, Td = convert_pidparams_to_standard(param_p, param_i, param_d, form) | ||
Kp, Ki, Kd = convert_pidparams_to_parallel(param_p, param_i, param_d, form) | ||
TE = Continuous() | ||
ia = Ti != Inf && Ti != 0 # integral action, 0 would result in division by zero, but typically indicates that the user wants no integral action | ||
if !isnothing(Tf) | ||
if ia | ||
if Ki != 0 | ||
A = [0 1 0; 0 0 1; 0 -2/Tf^2 -2/Tf] | ||
B = [0; 0; 1] | ||
C = 2 * Kp / Tf^2 * [1/Ti 1 Td] | ||
C = 2 / Tf^2 * [Ki Kp Kd] | ||
else | ||
A = [0 1; -2/Tf^2 -2/Tf] | ||
B = [0; 1] | ||
C = 2 * Kp / Tf^2 * [1 Td] | ||
C = 2 / Tf^2 * [Kp Kd] | ||
end | ||
D = 0 | ||
elseif Td == 0 | ||
if ia | ||
elseif Kd == 0 | ||
if Ki != 0 | ||
A = 0 | ||
B = 1 | ||
C = Kp / Ti # Ti == 0 would result in division by zero, but typically indicates that the user wants no integral action | ||
C = Ki # Ti == 0 would result in division by zero, but typically indicates that the user wants no integral action | ||
D = Kp | ||
else | ||
return ss([Kp]) | ||
|
@@ -98,7 +96,7 @@ | |
""" | ||
pidplots(P, args...; params_p, params_i, params_d=0, form=:standard, ω=0, grid=false, kwargs...) | ||
|
||
Plots interesting figures related to closing the loop around process `P` with a PID controller supplied in `params` | ||
Display the relevant plots related to closing the loop around process `P` with a PID controller supplied in `params` | ||
on one of the following forms: | ||
* `:standard` - `Kp*(1 + 1/(Ti*s) + Td*s)` | ||
* `:series` - `Kc*(1 + 1/(τi*s))*(τd*s + 1)` | ||
|
@@ -491,7 +489,7 @@ | |
verbose && ki < 0 && @warn "Calculated ki is negative, inspect the Nyquist plot generated with doplot = true and try adjusting ω or the angle ϕt" | ||
C = pid(kp, ki, kd, form=:parallel) | ||
any(real(p) > 0 for p in poles(C)) && @error "Calculated controller is unstable." | ||
kp, ki, kd = ControlSystemsBase.convert_pidparams_from_to(kp, ki, kd, :parallel, form) | ||
kp, ki, kd = convert_pidparams_from_to(kp, ki, kd, :parallel, form) | ||
CF = C*F | ||
fig = if doplot | ||
w = exp10.(LinRange(log10(ω)-2, log10(ω)+2, 1000)) | ||
|
@@ -511,26 +509,26 @@ | |
end | ||
|
||
""" | ||
Kp, Ti, Td = convert_pidparams_to_standard(param_p, param_i, param_d, form) | ||
Kp, Ti, Td = convert_pidparams_to_parallel(param_p, param_i, param_d, form) | ||
|
||
Convert parameters from form `form` to `:standard` form. | ||
Convert parameters from form `form` to `:parallel` form. | ||
|
||
The `form` can be chosen as one of the following | ||
* `:standard` - ``K_p(1 + 1/(T_i s) + T_ds)`` | ||
* `:series` - ``K_c(1 + 1/(τ_i s))(τ_d s + 1)`` | ||
* `:parallel` - ``K_p + K_i/s + K_d s`` | ||
""" | ||
function convert_pidparams_to_standard(param_p, param_i, param_d, form::Symbol) | ||
if form === :standard | ||
function convert_pidparams_to_parallel(param_p, param_i, param_d, form::Symbol) | ||
if form === :parallel | ||
return param_p, param_i, param_d | ||
elseif form === :series | ||
return @. ( | ||
param_p * (param_i + param_d) / param_i, | ||
param_i + param_d, | ||
param_i * param_d / (param_i + param_d) | ||
) | ||
elseif form === :parallel | ||
return @. (param_p, param_p / param_i, param_d / param_p) | ||
# param_i = 0 would result in division by zero, but typically indicates that the user wants no integral action | ||
param_i == 0 && return @. (param_p * (1, 0, param_d)) | ||
return @. (param_p * | ||
((param_i + param_d) / param_i, 1 / param_i, param_d)) | ||
elseif form === :standard | ||
param_i == 0 && return @. param_p * (1, 0, param_d) | ||
return @. param_p * (1, 1 / param_i, param_d) | ||
else | ||
throw(ArgumentError("form $(form) not supported.")) | ||
end | ||
|
@@ -550,22 +548,54 @@ | |
if form === :standard | ||
return Kp, Ti, Td | ||
elseif form === :series | ||
return @. ( | ||
(Ti - sqrt(Ti * (Ti - 4 * Td))) / 2 * Kp / Ti, | ||
(Ti - sqrt(Ti * (Ti - 4 * Td))) / 2, | ||
(Ti + sqrt(Ti * (Ti - 4 * Td))) / 2, | ||
) | ||
Δ = Ti * (Ti - 4 * Td) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These lines are broken for parameter arrays |
||
Δ < 0 && throw(DomainError("The condition Ti^2 ≥ 4Td*Ti is not satisfied: the PID parameters cannot be converted to series form")) | ||
sqrtΔ = sqrt(Δ) | ||
return @. ((Ti - sqrtΔ) / 2 * Kp / Ti, | ||
(Ti - sqrtΔ) / 2, | ||
(Ti + sqrtΔ) / 2) | ||
elseif form === :parallel | ||
return @. (Kp, Kp/Ti, Td*Kp) | ||
else | ||
throw(ArgumentError("form $(form) not supported.")) | ||
end | ||
end | ||
|
||
|
||
""" | ||
Kp, Ti, Td = convert_pidparams_from_parallel(param_p, param_i, param_d, to_form) | ||
|
||
Convert parameters from form `:parallel` to form `to_form`. | ||
|
||
The `form` can be chosen as one of the following | ||
* `:standard` - ``K_p(1 + 1/(T_i s) + T_ds)`` | ||
* `:series` - ``K_c(1 + 1/(τ_i s))(τ_d s + 1)`` | ||
* `:parallel` - ``K_p + K_i/s + K_d s`` | ||
""" | ||
function convert_pidparams_from_parallel(param_p, param_i, param_d, to::Symbol) | ||
if to === :parallel | ||
return param_p, param_i, param_d | ||
elseif to === :series | ||
param_i == 0 && return @. (param_p * (1, 0, param_d)) | ||
Δ = param_p^2-4param_i*param_d | ||
Δ < 0 && | ||
throw(DomainError("The condition KP^2 ≥ 4KI*KD is not satisfied: the PID parameters cannot be converted to series form")) | ||
sqrtΔ = sqrt(Δ) | ||
return @. ((param_p - sqrtΔ)/2, (param_p - sqrtΔ)/(2param_i), (param_p + sqrtΔ)/(2param_i)) | ||
elseif to === :standard | ||
param_p == 0 && throw(DomainError("Cannot convert to standard form when Kp=0")) | ||
param_i == 0 && return @. (param_p, Inf, param_d / param_p) | ||
return @. (param_p, param_p / param_i, param_d / param_p) | ||
else | ||
throw(ArgumentError("form $(form) not supported.")) | ||
end | ||
end | ||
|
||
|
||
""" | ||
convert_pidparams_from_to(kp, ki, kd, from::Symbol, to::Symbol) | ||
""" | ||
function convert_pidparams_from_to(kp, ki, kd, from::Symbol, to::Symbol) | ||
kp, ki, kd = convert_pidparams_to_standard(kp, ki, kd, from) | ||
convert_pidparams_from_standard(kp, ki, kd, to) | ||
kp, ki, kd = convert_pidparams_to_parallel(kp, ki, kd, from) | ||
convert_pidparams_from_parallel(kp, ki, kd, to) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to keep the old function as it was and just add the new function. We don't want to unnecessarily break any downstream package that might have use this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the phone so haven't checked, but iirc we neither have this in online docs or export it, so I probably wouldn't count it as api.
I think the thing that is exported is the
convert_pidparams_from_to
, though I could be wrong.But it is also easy to leave it as an extra method if you feel more comfortable with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to keep it for a few reasons
public
keyword) lacks a good way to indicate what is and isn't public facing API, and whether or not something is included in the docs is a policy that is awkward to work with from a user perspective. I thus wouldn't be surprised if other people have found this function and made use of it. It does have a nice docstring after all, and we haven't explicitly documented any policy for what is and isn't public API in ControlSystems.