import numpy as np
import matplotlib.pyplot as plt
import control
Standard Feedback loop
![]() |
Controller :
- Converts the error term into an actuator command
- We are free to choose any control scheme we like.
- As long as the closed loop performance of the system meets our requirements
Note: controller = compensator
- Lead and lag compensators are used quite extensively in control.
- A lead compensator can increase the stability or speed of reponse of a system;
- A lag compensator can reduce (but not eliminate) the steady-state error.
- Depending on the effect desired, one or more lead and lag compensators may be used in various combinations.
- Lead, lag, and lead/lag compensators are usually designed for a system in transfer function form.
- What is phase lead
![]() |
t = np.arange(0, 10, 0.1)
plt.plot(t, np.sin(t), color='r', label='sin');
plt.plot(t, np.cos(t), color='b', label='cos');
plt.legend();
plt.grid()
- The signal is ahead of the signal by deg
- The output leads the input by 90 deg
We can plot the Bode plots:
![]() |
- Differentiation gives positive phase
Integration gives negative phase (Mirrors the derivative plot)
A zero in a transfer function adds phase
A pole in a transfer function subtracts phase
Lead compensator: adds phase (at least in some frequency range of interest)
- Lag compensator: subtracts phase (at least in some frequency range of interest)
Let's look at the zero-pole contribution separately:
![]() |
- The zero adds 90 deg and amplifies high frequencies
- The pole subtracts 90 deg and attenuates high frequencies
- Multiplying the two T.F. together means adding everything together on the Bode plot
- Lead/Lag compensator:
- Behaves like a real zero early on, at low frequency
- Until the real pole pulls it back at high frequency
- See blue line for its approximate representation
![]() |
Note:
- A lead compensator increases the gain at high frequency (but less than a real zero would do)
- This means that it is less noisy than a derivative controller on its own
- A lead compensator adds phase between the two corner frequencies and no phase outside
- Moving the two frequency means we can change where we add our phase
Let's see an example:
w_z = 1
w_p = 10
s = control.tf([1, 0], [1])
R_s = w_p/(s+w_p)*(s+w_z/w_z)
We can plot the zero (blue) and the pole (orange) parts together with the combined Bode plot (green):
fig, axs = plt.subplots(1, figsize=(10,5))
# zero (blue)
control.bode_plot((s+w_z)/w_z, dB=True, omega_limits = [0.1, 100], wrap_phase =True);
# pole (orange)
control.bode_plot(w_p/(s+w_p), dB=True, omega_limits = [0.1, 100], wrap_phase =True);
# compensator (green)
control.bode_plot(R_s, dB=True, omega_limits = [0.1, 100], wrap_phase =True);
# Note: If wrap_phase is True the phase will be restricted to the range [-180, 180) (or [-\pi, \pi) radians)
- What happens when we move the zero closer to the pole?
w_z = 5
w_p = 10
fig, axs = plt.subplots(1, figsize=(10,5))
# zero (blue)
control.bode_plot((s+w_z)/w_z, dB=True, omega_limits = [0.1, 100], wrap_phase =True);
# pole (orange)
control.bode_plot(w_p/(s+w_p), dB=True, omega_limits = [0.1, 100], wrap_phase =True);
# compensator transfer function (green)
R_s = w_p/(s+w_p)*(s+w_z)/w_z
control.bode_plot(R_s, dB=True, omega_limits = [0.1, 100], wrap_phase =True);
- Still a phase lead is present, but much smaller
- What happens if the zero is right on top of the pole?
- if < we obtain a lag compensator
w_z = 50
w_p = 10
fig, axs = plt.subplots(1, figsize=(10,5))
# zero
control.bode_plot((s+w_z)/w_z, dB=True, omega_limits = [1, 1000], wrap_phase =True);
# pole
control.bode_plot(w_p/(s+w_p), dB=True, omega_limits = [1, 1000], wrap_phase =True);
# compensator transfer function
R_s = w_p/(s+w_p)*(s+w_z)/w_z
control.bode_plot(R_s, dB=True, omega_limits = [1, 1000], wrap_phase =True);
- The zero affects the system at higher frequency
- The system behaves like a real pole at lower frequency
- Until the zero comes into effect and "cancels" the pole at higher frequency
- This add lag to the system
Note: The sama transfer function structure can produce phase lead or lag, adjusting the relative position of the pole and the zero
- Design a compensator that uses both a lead and a lag compensator:
w_z = .5
w_p = 1
R_Lead = w_p/(s+w_p)*(s+w_z)/w_z
# Lag compensator
w_z1 = 15
w_p1 = 5
R_Lag = w_p1/(s+w_p1)*(s+w_z1)/w_z1
Plot the pole zero map for the Lead compensator:
control.pzmap(R_Lead);
Plot the pole zero map for the Lag compensator:
control.pzmap(R_Lag);
We construct the Lead-Lag compensator:
R_LL = R_Lead*R_Lag
And we can plot the Bode Plot to see what the phase does:
fig, axs = plt.subplots(1, figsize=(10,5))
control.bode_plot(R_LL, dB=True, omega_limits = [.01, 1000], wrap_phase =True);
![]() |
- This compensator is leading at low frequency, and lagging at higher frequency
- Let's consider our control loop again:
![]() |
We have a model of our plant
- we are given a transfer function
- we have identified the model
Design requirements - performance goal:
- Stability
- Rise time
- Settling time
- Max Overshoot
- Damping ratio
- Gain/Phase margin
alone does not meet our requirements
- We need to design the controller
- How do we choose ?
Let's consider:
- What is the Root Locus of ?
s = control.tf([1, 0],[1])
G_s = 1/((s+2)*(s+4))
fig, axs = plt.subplots(1, figsize=(10,5))
control.rlocus(G_s);
- What if we had a Lead compensator?
- How does the root locus change?
Let's choose, arbitrarily, a Lead compensator ():
Let's see how it looks like with Python:
We define the controller:
R_s = (s+5)/(s+6)
Plot the Root Locus:
fig, axs = plt.subplots(1, figsize=(10,5))
control.rlocus(G_s*R_s);
With a lead compensator:
- We have moved the asynmptotes further into the left half plane
- Increasing the gain the close loop poles would be more to the left: we have added stability to the system
How does this help us?
- When we use the root locus method we typically know where we would like our dominant closed loop poles to be so that we meet our requirements
- With the root locus method, we first convert our requirements into pole locations
- We need a lead compensator if we need to move our poles to the left of where our current (uncompensated) poles are
- We need a lag compensator if we need to move our poles to the right of where our current (uncompensated) poles are
- If the root locus already goes through the desired locations, we only need to choose the correct gain
- Note: we could still have a steady state error problem, but we know how to fix this already
Given desired poles, solving for the compensator becomes a trigonometry problem:
To be part of the Root Locus:
For example, for our system we saw that the root locus is:
![]() |
- There are no zeros (so we do not need to subtract)
- The sum of =180 because that point is part of the root locus
Given our system:
If we want poles:
![]() |
![]() |
And if we sum up all the angles: = = 225
- since
And of course, this is not on the Root Locus.
- To have it on the Root Locus, we need to remove 45 deg of phase
- Using a phase lead compensator we can do it adding a single pole and a single zero:
- is our lead compensator
- If we pick a zeros at -5
- The pole must go into one specific location:
![]() |
![]() |
The compensator for this particular problem is:
%matplotlib notebook
R_s = (s+5)/(s+10.015)
fig, axs = plt.subplots(1, figsize=(7,7))
control.rlocus(G_s*R_s);
One more step:
- We need to calculate the gain that moves the poles in closed loop where we want them
We can then find:
We can then sketch bounds:
</tr> </table>Rule fo thumb: place your zero at or closely to the left of the second real axis open loop pole
- Does not guarantee that overshoot requirements will be met
- Trial and error might be needed
- With higher order systems, might be difficult to predict where other non-dominant poles go: we must avoid making them unstable
- Faster responding system means responding to noise as well.
- one real pole and one real zero
- (gain)
We can re-write the above equation as:
- The transfer function has the same structure
- We can use the same design technique that we saw for the Lead Compensators
- With a Phase Lag compensator we can move our dominant poles closer to the imaginary axis
- This is however not the main reason Lag Compensators
- Lag Compensators are useful to address steady state errors
- e.g., error to the step input
- improve steady state errors, without changing the position of the dominant poles (they are already where we need them)
- this means we do not want shape the root locus very much
![]() |
![]() |
Where:
and our Lag compensator is:
and the input is .
The steady state error for the uncompensated system is:
For a step input:
The steady state error for the compensated system is:
For a step input:
- We can choose and have the corresponding
- To have ,
- We can only reduce the steady state error and not eliminate it. We need to change the system type to eliminate it
- We know the zero/pole ratio
- Where do we place them?
- E.g., , or , would work
Remember that the Root Locus is where the angles of poles (+) and zeros (-) add to 180 degrees
![]() |
- We do not want to move the location of our roots too much when using a Lag compensator
- The the angle of the poles and zeros of the compensator should be very small (no Root Locus shaping):
- Meeting the condition above while keeping the desired is much easier if we place the zero and pole very close to the imaginary axis
- Practical constraints means you cannot move them too close (resistors and capacitors limits)
Rule of thumb: the location of the zero is approximately 50 times closer to the imaginary axis as the closer dominant pole
E.g.,
- dominant poles at
- zero at
- pole at
- We can have the Lag compensator in series with a Lead compensator
Exercise:
- Plot the step response and the impulse respose of the uncompensated and compensated system
s = control.tf([1, 0],[1])
sys_u = 1/((s+1)*(s+3))*(16*(s+4)/(s+9))
print('sys_u', sys_u)
sys_c = 1/((s+1)*(s+3))*(16*(s+4)/(s+9))*(s+0.06)/(s+0.016)
print('sys_c', sys_c)
t_u, yout_u = control.step_response(control.feedback(sys_u, 1), T=100)
t_c, yout_c = control.step_response(control.feedback(sys_c, 1), T=100)
fig, ax = plt.subplots(1, figsize=(10,7))
plt.plot(t_u, yout_u, color='b', label='uncompensated');
plt.plot(t_c, yout_c, color='r', label='compensated');
plt.grid();
plt.legend();
plt.yticks(np.arange(0, 1, 0.1));
- As expected: the uncompensated system has
- The compensated system has
- Settling time does not change very much, as desired
We can see this better looking at the impulse response
t_u, yout_u = control.impulse_response(control.feedback(sys_u, 1), T=3)
t_c, yout_c = control.impulse_response(control.feedback(sys_c, 1), T=3)
fig, ax = plt.subplots(1, figsize=(10,7))
plt.plot(t_u, yout_u, color='b', label='uncompensated');
plt.plot(t_c, yout_c, color='r', label='compensated');
plt.grid();
plt.legend();
plt.yticks(np.arange(0, 1.5, 0.1));
- This confirmes that we did not change the position of the dominant poles very much
- Let's consider our control loop again:
![]() |
- Compensator: Lead compensator, so we need to select one pole and one zero
- First: Convert requirements to frequency domain requirements if needed
- Gain/Phase margin
- Bandwidth
- Gain crossover frequency
- Zero-frequency magnitude or DC Gain
- Steady-state error
![]() |
A phase-lead compensator can also be designed using a frequency response approach. A lead compensator in frequency response form is given by the following transfer function:
with (when we would have a lag compensator).
Note that the previous expression is equivalent to the form (which we used for the Root Locus):
when , , and .
In frequency response design, the phase-lead compensator adds positive phase to the system over the frequency range to .
And a Bode plot of a phase-lead compensator has the following form:
![]() |
- The two corner frequencies are at and
Note the positive phase that is added to the system between these two frequencies.
Depending on the value of , the maximum added phase can be up to 90 degrees
- If you need more than 90 degrees of phase, two lead compensators in series can be employed.
- For a ramp unit (assuming a stable system):
- we need at least a type 1 system to have a finite error (single pole at the origin)
- we need at least a type 2 system to have a zero error
Note we cannot meet our requirements with a lead compensator alone. It does not have a pole at the origin
Our controller needs to have this structure to start from
and now we can add the lead compensator to deal with the phase margin.
It is always better to start from the type of the system: adding a pole at the origin will affect your phase.
Given that a single pole at the origin is enough, we will not add a second one
- Adds complexity
- Might reduce stability
- Do not overdesign!
- Choose the gain to meet
For an input :
where, in this case:
If we plug in the numbers:
We can then choose:
Let's now draw the Bode plot:
- Bring the system in the Bode form
- Sketch the Bode Diagram for each individual part
- Add them all up
![]() |
Let's check it with Python
s = control.tf([1, 0], [1])
G_s = 1/(0.2*s+1)
R_s = 50/s
fig, ax = plt.subplots(1, figsize=(10,7))
control.bode_plot(G_s*R_s, dB=True);
[gm, pm, _, pm_f] = control.margin(G_s*R_s)
print('Phase margin: deg', pm, 'at rad/s', pm_f)
We do not respect our phase margin requirement
- A lead compensator adds phase for a specific frequency range
- We also add gain, which means we move the crossover frequency to higher frequency
![]() |
- To determin how much phase let's look at the lead compensator equations, which we can re-write in this form to highlight the relative relationship between the pole and the zero:
- In this form, the steady state gain is 1 (0 dB). Does not affect the steady state we already determined.
- to be a lead compensator
- is a lag compensator
From the equation above it is easy to verfy that the following equations can be used to determine significant points of the response:
Upper cutoff frequency
- gain starts to increase
Lower cutoff frequency
- gain starts to flatten out
Max phase (obtained at the center frequency )
Freq at Max phase
Gain at Max phase
![]() |
Choosing and :
- Choose the maximum phase you would like to add and solve for
- Choose the frequency where you would like to add and solve for
- We would get a lower phase increase
- Trial and error is an option
- Add a safety factor (e.g., 15 deg)
Let's go back to our design
- We need 30 deg more at 15 rad/s (from 18 to 38)
- We add some safety factor:
Freq at Max phase
Final controller:
And we should verify that we obtain the desired phase margin plotting the Bode plot.
Let's plot the Bode plots for
the initial controller (in blue)
the final lead compensator (in orange)
s = control.tf([1, 0], [1])
G_s = 1/(0.2*s+1)
R_s = 50/s*(0.088*s+1)/(0.022*s+1)
fig, ax = plt.subplots(1, figsize=(10,7))
control.bode_plot(G_s*50/s, dB=True, wrap_phase=True, omega_limits=[0.1, 1000]);
control.bode_plot(G_s*R_s, dB=True, margins=True, wrap_phase=True, omega_limits=[0.1, 1000]);
- The gain plot did not change too much
- We can see the lead compensator as a delta that we sum to the uncompensated Bode plot
- We can only add up to deg (there is only one zero)
In practice: deg
More phase lead needed means two lead compensators in series
- Let's consider our control loop again:
![]() |
System requirements
- Steady state error < 0.02 to a unit ramp input
- Phase margin deg
When we designed the Lead compensator using the Bode plots:
- We saw we need a type 1 system at least, and then we chose the gain to meet our steady state error requirement
We designed an initial controller:
- Achieves requirement 1, but not 2
s = control.tf([1, 0], [1])
G_s = 1/(0.2*s+1)
R_s = 50/s
fig, ax = plt.subplots(1, figsize=(10,7))
control.bode_plot(G_s*R_s, dB=True, margins=True, wrap_phase=True, omega_limits=[0.1, 1000]);
- Before, we used a Lead compensator to meet our phase requirement, with the final controller that was:
We could use a Lag compensator to meet our phase margin requirement
But with the Lag compensator we meet the requirement changing the gain crossover frequency
Note that we want to still retain the performance we obtained before with the partially compensated system (i.e., when we were using the controller . This means that we do not want to change the DC gain because that has been set to achieve the steady state requirements
Let's consider a typical Lag Compensator Bode Plot.
And to do so, we consider:
fig, ax = plt.subplots(1, figsize=(10,7))
control.bode_plot((2*s+1)/(4*s+1), dB=True, wrap_phase=True);
- A low frequency, the gain is 1 (0 dB)
- Useful because we do not want to change our DC gain
- At high frequency, the magnitude is 1/2 or -6dB (for this specific choice of zero/pole)
- We want to leverage the high frequency attentuation with a relative flat frequency shift to move the crossover frequency and the 0dB DC gain at low frequency not to impact the steady state regime
- This means:
- We need to have the high frequency attenuation in the frequency range of the Bode plot that we want to shape
- We need to push the phase lag to lower frequencies as much as possible
Let's go back to our Bode Plot
# s = control.tf([1, 0], [1])
# G_s = 1/(0.2*s+1)
# R_s = 50/s
# fig, ax = plt.subplots(1, figsize=(10,7))
# control.bode_plot(G_s*R_s, dB=True, margins=True, wrap_phase=True, omega_limits=[0.1, 1000]);
![]() |
- We would need to decrease the gain by about 18 dB to have a crossover frequency with a 48 degree phase margin
- Add a safety margin (e.g., drop the gain by 20 dB to add some safety)
- Let's calculate how to have a drop in gain of 20dB or 10 at high frequency:
or, the relative ratio between the zero and the pole is 10:
- We want the phase lag as low frequency as possible.
- We need the zero and poles as close to the imaginary axis as possible (or as large as possible)
- The larger , the closer to the imaginary axis is the pole-zero pair, the less the lag compensator interfere with the original system, while still acting to improve our phase margin
- This is the same approach that we used for the Root Locus to design the Lag compensator
Rule of thumb: place the zero 50 times closer to the origin as the dominant poles
For our case, we have a pole at , which means we would like a zero at , or if we use the time-constant representation :
And here is the final controller:
s = control.tf([1, 0], [1])
# System G(s)
G_s = 1/(0.2*s+1)
# Partial Compensator P(s)
P_s = 50/s
# Lead Compensator Lead(s)
Lead_s = (0.088*s+1)/(0.022*s+1)
# Lag Compensator Lag(s)
Lag_s = (10*s+1)/(100*s+1)
# Lead Lag compensator
R_s = 50/s*Lead_s*Lag_s
# create the axis and figure
fig, ax = plt.subplots(1, figsize=(10,7))
# Blue, uncompensated
control.bode_plot(P_s*G_s, dB=True, margins=True, wrap_phase=True, omega_limits=[0.0001, 1000], color='b');
# Green, Lead compensated
control.bode_plot(P_s*G_s*Lead_s, dB=True, margins=True, wrap_phase=True, omega_limits=[0.0001, 1000], color='g');
# Red, Lag compensated
control.bode_plot(P_s*G_s*Lag_s, dB=True, margins=True, wrap_phase=True, omega_limits=[0.0001, 1000], color='r');
- Compare the phase maring of both the Lead Compensated, and the Lag Compensated
- At low frequency the DC gain has not changed for any system
- With the Lag compensator we moved the cross-over frequency to lower frequency: we slowed down the system
We can see this plotting the step response
t_o, yout_o = control.step_response(control.feedback(P_s*G_s, 1), T=5)
t_lead, yout_lead = control.step_response(control.feedback(P_s*G_s*Lead_s, 1), T=5)
t_lag, yout_lag = control.step_response(control.feedback(P_s*G_s*Lag_s, 1), T=5)
fig, ax = plt.subplots(1, figsize=(10,7))
plt.plot(t_o, yout_o, color='b', label='Uncompensated');
plt.plot(t_lead, yout_lead, color='g', label='Lead compensated');
plt.plot(t_lag, yout_lag, color='r', label='Lag compensated');
plt.grid();
plt.legend();
plt.yticks(np.arange(0, 2, 0.1));
- The original system is less stable
- The Lead compensated is very fast
- The Lag compensated is slower
Note:
- A slower system does not react to high frequency noise as much, and this tends to be better
- If we do not need to track fast signals, a slower system can be better
Final comments
- This type of lead/lag compensators is designed in the frequency domain by determining from the amount of phase needed to satisfy the phase margin requirements, and determing to place the added phase at the new gain-crossover frequency.
- The lead compensator increases the gain of the system at high frequencies (the amount of this gain is equal to ). This can increase the crossover frequency, which will help to decrease the rise time and settling time of the system (but may amplify high frequency noise).
- A lead-lag compensator combines the effects of a lead compensator with those of a lag compensator. The result is a system with improved transient response, stability, and steady-state error.
- To implement a lead-lag compensator, first design the lead compensator to achieve the desired transient response and stability, and then design a lag compensator to improve the steady-state response of the lead-compensated system.