Skip to content

Commit 27aebf1

Browse files
baggepinnenclaude
andcommitted
add tests, docstring, and export for loopshapingPD
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 62bed5a commit 27aebf1

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

lib/ControlSystemsBase/src/pid_design.jl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export pid, pid_tf, pid_ss, pid_2dof, pid_ss_2dof, pidplots, leadlink, laglink, leadlinkat, leadlinkcurve, stabregionPID, loopshapingPI, placePI, loopshapingPID
1+
export pid, pid_tf, pid_ss, pid_2dof, pid_ss_2dof, pidplots, leadlink, laglink, leadlinkat, leadlinkcurve, stabregionPID, loopshapingPI, placePI, loopshapingPID, loopshapingPD
22
export convert_pidparams_from_parallel, convert_pidparams_from_standard, convert_pidparams_from_to, convert_pidparams_to_parallel, convert_pidparams_to_standard
33

44
"""
@@ -519,6 +519,27 @@ function loopshapingPI(P0, ωp; ϕl=0, rl=0, phasemargin=0, form::Symbol=:standa
519519
(; C, kp, ki, fig, CF)
520520
end
521521

522+
"""
523+
C, kp, kd, fig, CF = loopshapingPD(P, ωp; ϕl, rl, phasemargin, form=:standard, doplot=false, Tf, F)
524+
525+
Selects the parameters of a PD-controller (on parallel form) such that the Nyquist curve of `P` at the frequency `ωp` is moved to `rl exp(i ϕl)`
526+
527+
The parameters can be returned as one of several common representations
528+
chosen by `form`, the options are
529+
* `:standard` - ``K_p(1 + 1/(T_i s) + T_d s)``
530+
* `:series` - ``K_c(1 + 1/(τ_i s))(τ_d s + 1)``
531+
* `:parallel` - ``K_p + K_i/s + K_d s``
532+
533+
If `phasemargin` is supplied (in degrees), `ϕl` is selected such that the curve is moved to an angle of `phasemargin - 180` degrees
534+
535+
If no `rl` is given, the magnitude of the curve at `ωp` is kept the same and only the phase is affected, the same goes for `ϕl` if no phasemargin is given.
536+
537+
- `Tf`: An optional time constant for second-order measurement noise filter on the form `tf(1, [Tf^2, 2*Tf/sqrt(2), 1])` to make the controller strictly proper.
538+
- `F`: A pre-designed filter to use instead of the default second-order filter that is used if `Tf` is given.
539+
- `doplot` plot the `gangoffourplot` and `nyquistplot` of the system.
540+
541+
See also [`loopshapingPI`](@ref), [`loopshapingPID`](@ref), [`pidplots`](@ref), [`stabregionPID`](@ref) and [`placePI`](@ref).
542+
"""
522543
function loopshapingPD(P0, ωp; ϕl=0, rl=0, phasemargin=0, form::Symbol=:standard, doplot=false, Tf = nothing, F=nothing)
523544
issiso(P0) || throw(ArgumentError("P must be SISO"))
524545
if F === nothing && Tf !== nothing

lib/ControlSystemsBase/test/test_pid_design.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,43 @@ _,_,_,pm = margin(P*CF)
142142
@test pm[] > 45*0.99
143143

144144

145+
# Test loopshapingPD
146+
P = tf(1,[1, 1, 1])
147+
148+
# Basic phase-margin spec — mirrors the loopshapingPI block above
149+
C, kp, kd = loopshapingPD(P, 10; phasemargin = 30, doplot = false)
150+
_,_,_,pm = margin(P*C)
151+
@test pm[] > 30*0.99
152+
153+
# With second-order noise filter Tf and doplot=true (exercises the plot branch and 5-tuple return)
154+
C, kp, kd, fig, CF = loopshapingPD(P, 1; phasemargin = 45, doplot = true, Tf = 0.1)
155+
_,_,_,pm = margin(P*CF)
156+
@test pm[] > 45*0.99
157+
158+
# Design-point property: with default rl, L(jωp) has |P(jωp)| magnitude and angle -180°+phasemargin
159+
ωp = 2.0
160+
pm_spec = 60
161+
C, kp, kd, fig, CF = loopshapingPD(P, ωp; phasemargin = pm_spec)
162+
L_at_ωp = freqresp(P*CF, ωp)[]
163+
@test abs(L_at_ωp) abs(freqresp(P, ωp)[]) rtol=1e-6
164+
@test rad2deg(angle(L_at_ωp)) -180 + pm_spec rtol=1e-6
165+
166+
# Explicit rl, ϕl spec — verifies the rl/ϕl branch
167+
rl_spec = 0.5
168+
ϕl_spec = deg2rad(-150)
169+
C, kp, kd = loopshapingPD(P, ωp; rl = rl_spec, ϕl = ϕl_spec)
170+
L_at_ωp = freqresp(P*C, ωp)[]
171+
@test abs(L_at_ωp) rl_spec rtol=1e-6
172+
@test angle(L_at_ωp) ϕl_spec rtol=1e-6
173+
174+
# SISO guard
175+
@test_throws ArgumentError loopshapingPD(ssrand(2,2,2), 1.0)
176+
177+
# form = :parallel returns kp, kd that reconstruct C directly
178+
C, kp, kd = loopshapingPD(P, ωp; phasemargin = 45, form = :parallel)
179+
@test freqresptest(C, pid(kp, 0, kd, form=:parallel)) < 1e-10
180+
181+
145182
# Test placePI
146183
P = tf(1,[1, 1])
147184
C, Kp, Ti = placePI(P, 2, 0.7; form=:standard)

0 commit comments

Comments
 (0)