Skip to content

Commit

Permalink
Add phase argument for Bode plots
Browse files Browse the repository at this point in the history
  • Loading branch information
mph- committed Jul 26, 2024
1 parent 89ed251 commit 9d3649a
Show file tree
Hide file tree
Showing 15 changed files with 57 additions and 35 deletions.
Binary file modified doc/examples/discretetime/pade-step-delay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified doc/examples/discretetime/pade-step-delay2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions doc/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2587,14 +2587,15 @@ Bode plots are similar to frequency domain plots but plot both the magnitude (in
:width: 12cm

By default, a Bode plot of a Laplace domain expression is shown with
linear frequencies (in hertz). This can be changed to angular
frequencies (in radians) using the `var=omega` argument to the
`bode_plot()` method.
linear frequencies (in hertz) and phase in degrees. The frequency can
be changed to angular frequencies (in radians) using the `var=omega`
argument to the `bode_plot()` method. The phase can be plotted in
degrees using `phase='degrees'` or ignored with `phase=None`.

The frequency response of Laplace domain expressions is calculated
using the substitution :math:`s = j \omega`. Note, this does not
correctly calculate the DC component for expressions that have an
unstable impulse response.
unstable or marginally stable impulse response.


Nyquist plots
Expand Down
6 changes: 4 additions & 2 deletions doc/systems.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ Butterworth filters

Butterworth filters are created with the `Butterworth` class method. For example::

>>> B = Butterworth(order=2, Wn=omega0, btype='lowpass')
>>> from lcapy.ltifilter import Butterworth
>>> B = Butterworth(N=2, Wn=omega0, btype='lowpass')
>>> B.transfer_function()
2
ω₀
Expand Down Expand Up @@ -173,7 +174,8 @@ Bessel filters

Bessel filters are created with the `Bessel` class method. For example::

>>> B = Bessel(order=2, Wn=omega0, btype='lowpass')
>>> from lcapy.ltifilter import Bessel
>>> B = Bessel(N=2, Wn=omega0, btype='lowpass')
>>> B.transfer_function()
2
3⋅ω₀
Expand Down
4 changes: 2 additions & 2 deletions lcapy/fexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def plot(self, fvector=None, plot_type=None, **kwargs):
from .plot import plot_frequency
return plot_frequency(self, fvector, plot_type=plot_type, **kwargs)

def bode_plot(self, fvector=None, unwrap=True, **kwargs):
def bode_plot(self, fvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). fvector
specifies the frequencies. If it is a tuple (f1, f2), it sets
Expand All @@ -184,7 +184,7 @@ def bode_plot(self, fvector=None, unwrap=True, **kwargs):
"""

from .plot import plot_bode
return plot_bode(self, fvector, unwrap=unwrap, **kwargs)
return plot_bode(self, fvector, unwrap=unwrap, phase=phase, **kwargs)

def nyquist_plot(self, fvector=None, log_frequency=True, **kwargs):
"""Plot frequency response as a Nyquist plot (imaginary part versus
Expand Down
7 changes: 4 additions & 3 deletions lcapy/jfexpr.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""This module provides the FrequencyResponseDomainExpression class to
represent jf-domain ( frequency frequency domain) expressions.
Copyright 2022 Michael Hayes, UCECE
Copyright 2022--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -127,7 +127,7 @@ def plot(self, vvector=None, **kwargs):
from .plot import plot_frequency
return plot_frequency(self, vvector, **kwargs)

def bode_plot(self, vvector=None, unwrap=True, **kwargs):
def bode_plot(self, vvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). vvector
specifies the frequencies. If it is a tuple (f1, f2), it sets
Expand All @@ -140,7 +140,8 @@ def bode_plot(self, vvector=None, unwrap=True, **kwargs):
"""

from .plot import plot_bode
return plot_bode(self, vvector, unwrap=unwrap, **kwargs)
return plot_bode(self, vvector, unwrap=unwrap, phase=phase,
**kwargs)

def nyquist_plot(self, vvector=None, log_frequency=True, **kwargs):
"""Plot frequency response as a Nyquist plot (imaginary part versus
Expand Down
7 changes: 4 additions & 3 deletions lcapy/jomegaexpr.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""This module provides the AngularFrequencyResponseDomainExpression class to
represent jomega-domain (angular frequency frequency domain) expressions.
Copyright 2022 Michael Hayes, UCECE
Copyright 2022--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -128,7 +128,7 @@ def plot(self, wvector=None, **kwargs):
from .plot import plot_angular_frequency
return plot_angular_frequency(self, wvector, **kwargs)

def bode_plot(self, wvector=None, unwrap=True, **kwargs):
def bode_plot(self, wvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). wvector
specifies the angular frequencies. If it is a tuple (f1, f2), it sets
Expand All @@ -141,7 +141,8 @@ def bode_plot(self, wvector=None, unwrap=True, **kwargs):
"""

from .plot import plot_angular_bode
return plot_angular_bode(self, wvector, unwrap=unwrap, **kwargs)
return plot_angular_bode(self, wvector, unwrap=unwrap,
phase=phase, **kwargs)

def nyquist_plot(self, wvector=None, log_frequency=True, **kwargs):
"""Plot frequency response as a Nyquist plot (imaginary part versus
Expand Down
2 changes: 1 addition & 1 deletion lcapy/ltifilter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""This module provides continuous-time filter support.
Copyright 2022-2023 Michael Hayes, UCECE
Copyright 2022-2024 Michael Hayes, UCECE
"""

Expand Down
7 changes: 4 additions & 3 deletions lcapy/normfexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
to represent F-domain (normalized Fourier domain)
expressions.
Copyright 2021--2022 Michael Hayes, UCECE
Copyright 2021--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -149,7 +149,7 @@ def plot(self, Fvector=None, plot_type=None, **kwargs):
return plot_frequency(self, Fvector, plot_type=plot_type,
norm=True, **kwargs)

def bode_plot(self, fvector=None, unwrap=True, **kwargs):
def bode_plot(self, fvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). fvector
specifies the normalised frequencies. If it is a tuple (f1,
Expand All @@ -163,7 +163,8 @@ def bode_plot(self, fvector=None, unwrap=True, **kwargs):
"""

from .plot import plot_bode
return plot_bode(self, fvector, norm=True, unwrap=unwrap, **kwargs)
return plot_bode(self, fvector, norm=True, unwrap=unwrap,
phase=phase, **kwargs)

def nyquist_plot(self, fvector=None, log_frequency=False, **kwargs):
"""Plot frequency response as a Nyquist plot (imaginary part versus
Expand Down
6 changes: 3 additions & 3 deletions lcapy/normomegaexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
to represent Omega-domain (normalized angular Fourier domain)
expressions.
Copyright 2021--2022 Michael Hayes, UCECE
Copyright 2021--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -150,7 +150,7 @@ def plot(self, Wvector=None, plot_type=None, **kwargs):
return plot_angular_frequency(self, Wvector, plot_type=plot_type,
norm=True, **kwargs)

def bode_plot(self, Wvector=None, unwrap=True, **kwargs):
def bode_plot(self, Wvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). Wvector
specifies the normalised angular frequencies. If it is a
Expand All @@ -166,7 +166,7 @@ def bode_plot(self, Wvector=None, unwrap=True, **kwargs):

from .plot import plot_angular_bode
return plot_angular_bode(self, Wvector, norm=True, unwrap=unwrap,
**kwargs)
phase=phase, **kwargs)

def nyquist_plot(self, Wvector=None, log_frequency=False, **kwargs):
"""Plot frequency response as a Nyquist plot (imaginary part versus
Expand Down
5 changes: 3 additions & 2 deletions lcapy/omegaexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def plot(self, wvector=None, **kwargs):
from .plot import plot_angular_frequency
return plot_angular_frequency(self, wvector, **kwargs)

def bode_plot(self, wvector=None, unwrap=True, **kwargs):
def bode_plot(self, wvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). wvector
specifies the angular frequencies. If it is a tuple (f1, f2), it sets
Expand All @@ -164,7 +164,8 @@ def bode_plot(self, wvector=None, unwrap=True, **kwargs):
"""

from .plot import plot_angular_bode
return plot_angular_bode(self, wvector, unwrap=unwrap, **kwargs)
return plot_angular_bode(self, wvector, unwrap=unwrap,
phase=phase, **kwargs)

def nyquist_plot(self, wvector=None, log_frequency=True, **kwargs):
"""Plot frequency response as a Nyquist plot (imaginary part versus
Expand Down
6 changes: 3 additions & 3 deletions lcapy/phasor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
domain by substituting jomega (or jw) for s, where omega is the
angular frequency of the phasor.
Copyright 2014--2023 Michael Hayes, UCECE
Copyright 2014--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -444,7 +444,7 @@ def plot(self, fvector=None, **kwargs):

return plot_angular_frequency(self, fvector, **kwargs)

def bode_plot(self, fvector=None, unwrap=True, **kwargs):
def bode_plot(self, fvector=None, unwrap=True, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations). fvector
specifies the frequencies. If it is a tuple (m1, m2), it sets the
Expand All @@ -455,7 +455,7 @@ def bode_plot(self, fvector=None, unwrap=True, **kwargs):

from .plot import plot_bode

return plot_bode(self, fvector, unwrap=unwrap, **kwargs)
return plot_bode(self, fvector, unwrap=unwrap, phase=phase, **kwargs)


def phasor(arg, omega=None, **assumptions):
Expand Down
19 changes: 16 additions & 3 deletions lcapy/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,15 +565,28 @@ def plot_frequency(obj, f, plot_type=None, **kwargs):
return ax, ax2


def plot_bode(obj, f, **kwargs):
"""This is a helper function for a Bode plot. It is
better to use the `bode_plot()` method of a FourierDomainExpression."""
def plot_bode(obj, f, phase='radians', **kwargs):
"""This is a helper function for a Bode plot. It is better to use the
`bode_plot()` method of a FourierDomainExpression or
LaplaceDomainExpression.
"""

if 'log_frequency' not in kwargs:
kwargs['log_frequency'] = True
if 'unwrap' not in kwargs:
kwargs['unwrap'] = True

if 'plot_type' not in kwargs:
if phase == 'degrees':
kwargs['plot_type'] = 'dB-phase-degrees'
elif phase == 'radians':
kwargs['plot_type'] = 'dB-phase-radians'
elif phase is None:
kwargs['plot_type'] = 'dB'
else:
raise ValueError("phase must be 'radians', 'degrees', or None")

return plot_frequency(obj, f, log_magnitude=True, **kwargs)


Expand Down
6 changes: 3 additions & 3 deletions lcapy/sexpr.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""This module provides the LaplaceDomainExpression class to represent
s-domain (Laplace domain) expressions.
Copyright 2014--2023 Michael Hayes, UCECE
Copyright 2014--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -538,7 +538,7 @@ def pole_zero_plot(self, **kwargs):
return self.plot(**kwargs)

def bode_plot(self, fvector=None, unwrap=True, var=None, strict=False,
**kwargs):
phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations).
Expand All @@ -559,7 +559,7 @@ def bode_plot(self, fvector=None, unwrap=True, var=None, strict=False,
"""

H = self.frequency_response_evaluate(var=var)
return H.bode_plot(fvector, unwrap=unwrap, **kwargs)
return H.bode_plot(fvector, unwrap=unwrap, phase=phase, **kwargs)

def nyquist_plot(self, fvector=None, var=None, **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Nyquist
Expand Down
8 changes: 5 additions & 3 deletions lcapy/zexpr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""This module provides the ZDomainExpression class to represent z-domain expressions.
Copyright 2020--2022 Michael Hayes, UCECE
Copyright 2020--2024 Michael Hayes, UCECE
"""

Expand Down Expand Up @@ -209,7 +209,7 @@ def plot(self, t=None, **kwargs):
from .plot import plot_pole_zero
return plot_pole_zero(self, **kwargs)

def bode_plot(self, fvector=None, **kwargs):
def bode_plot(self, fvector=None, phase='radians', **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Bode
plot (but without the straight line approximations), assumong
`dt=1`. fvector specifies the frequencies. If it is a tuple
Expand All @@ -221,7 +221,9 @@ def bode_plot(self, fvector=None, **kwargs):
"""
from .discretetime import dt

return self.DTFT(causal=True).subs(dt, 1).bode_plot(fvector, **kwargs)
return self.DTFT(causal=True).subs(dt, 1).bode_plot(fvector,
phase=phase,
**kwargs)

def nyquist_plot(self, fvector=None, log_frequency=False, **kwargs):
"""Plot frequency response for a frequency-domain phasor as a Nyquist
Expand Down

0 comments on commit 9d3649a

Please sign in to comment.