123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961 |
- from sympy.core.numbers import I, pi
- from sympy.functions.elementary.exponential import (exp, log)
- from sympy.polys.partfrac import apart
- from sympy.core.symbol import Dummy
- from sympy.external import import_module
- from sympy.functions import arg, Abs
- from sympy.integrals.laplace import _fast_inverse_laplace
- from sympy.physics.control.lti import SISOLinearTimeInvariant
- from sympy.plotting.plot import LineOver1DRangeSeries
- from sympy.polys.polytools import Poly
- from sympy.printing.latex import latex
- __all__ = ['pole_zero_numerical_data', 'pole_zero_plot',
- 'step_response_numerical_data', 'step_response_plot',
- 'impulse_response_numerical_data', 'impulse_response_plot',
- 'ramp_response_numerical_data', 'ramp_response_plot',
- 'bode_magnitude_numerical_data', 'bode_phase_numerical_data',
- 'bode_magnitude_plot', 'bode_phase_plot', 'bode_plot']
- matplotlib = import_module(
- 'matplotlib', import_kwargs={'fromlist': ['pyplot']},
- catch=(RuntimeError,))
- numpy = import_module('numpy')
- if matplotlib:
- plt = matplotlib.pyplot
- if numpy:
- np = numpy
- def _check_system(system):
- """Function to check whether the dynamical system passed for plots is
- compatible or not."""
- if not isinstance(system, SISOLinearTimeInvariant):
- raise NotImplementedError("Only SISO LTI systems are currently supported.")
- sys = system.to_expr()
- len_free_symbols = len(sys.free_symbols)
- if len_free_symbols > 1:
- raise ValueError("Extra degree of freedom found. Make sure"
- " that there are no free symbols in the dynamical system other"
- " than the variable of Laplace transform.")
- if sys.has(exp):
-
-
- raise NotImplementedError("Time delay terms are not supported.")
- def pole_zero_numerical_data(system):
- """
- Returns the numerical data of poles and zeros of the system.
- It is internally used by ``pole_zero_plot`` to get the data
- for plotting poles and zeros. Users can use this data to further
- analyse the dynamics of the system or plot using a different
- backend/plotting-module.
- Parameters
- ==========
- system : SISOLinearTimeInvariant
- The system for which the pole-zero data is to be computed.
- Returns
- =======
- tuple : (zeros, poles)
- zeros = Zeros of the system. NumPy array of complex numbers.
- poles = Poles of the system. NumPy array of complex numbers.
- Raises
- ======
- NotImplementedError
- When a SISO LTI system is not passed.
- When time delay terms are present in the system.
- ValueError
- When more than one free symbol is present in the system.
- The only variable in the transfer function should be
- the variable of the Laplace transform.
- Examples
- ========
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import pole_zero_numerical_data
- >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s)
- >>> pole_zero_numerical_data(tf1) # doctest: +SKIP
- ([-0.+1.j 0.-1.j], [-2. +0.j -0.5+0.8660254j -0.5-0.8660254j -1. +0.j ])
- See Also
- ========
- pole_zero_plot
- """
- _check_system(system)
- system = system.doit()
- num_poly = Poly(system.num, system.var).all_coeffs()
- den_poly = Poly(system.den, system.var).all_coeffs()
- num_poly = np.array(num_poly, dtype=np.complex128)
- den_poly = np.array(den_poly, dtype=np.complex128)
- zeros = np.roots(num_poly)
- poles = np.roots(den_poly)
- return zeros, poles
- def pole_zero_plot(system, pole_color='blue', pole_markersize=10,
- zero_color='orange', zero_markersize=7, grid=True, show_axes=True,
- show=True, **kwargs):
- r"""
- Returns the Pole-Zero plot (also known as PZ Plot or PZ Map) of a system.
- A Pole-Zero plot is a graphical representation of a system's poles and
- zeros. It is plotted on a complex plane, with circular markers representing
- the system's zeros and 'x' shaped markers representing the system's poles.
- Parameters
- ==========
- system : SISOLinearTimeInvariant type systems
- The system for which the pole-zero plot is to be computed.
- pole_color : str, tuple, optional
- The color of the pole points on the plot. Default color
- is blue. The color can be provided as a matplotlib color string,
- or a 3-tuple of floats each in the 0-1 range.
- pole_markersize : Number, optional
- The size of the markers used to mark the poles in the plot.
- Default pole markersize is 10.
- zero_color : str, tuple, optional
- The color of the zero points on the plot. Default color
- is orange. The color can be provided as a matplotlib color string,
- or a 3-tuple of floats each in the 0-1 range.
- zero_markersize : Number, optional
- The size of the markers used to mark the zeros in the plot.
- Default zero markersize is 7.
- grid : boolean, optional
- If ``True``, the plot will have a grid. Defaults to True.
- show_axes : boolean, optional
- If ``True``, the coordinate axes will be shown. Defaults to False.
- show : boolean, optional
- If ``True``, the plot will be displayed otherwise
- the equivalent matplotlib ``plot`` object will be returned.
- Defaults to True.
- Examples
- ========
- .. plot::
- :context: close-figs
- :format: doctest
- :include-source: True
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import pole_zero_plot
- >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s)
- >>> pole_zero_plot(tf1) # doctest: +SKIP
- See Also
- ========
- pole_zero_numerical_data
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Pole%E2%80%93zero_plot
- """
- zeros, poles = pole_zero_numerical_data(system)
- zero_real = np.real(zeros)
- zero_imag = np.imag(zeros)
- pole_real = np.real(poles)
- pole_imag = np.imag(poles)
- plt.plot(pole_real, pole_imag, 'x', mfc='none',
- markersize=pole_markersize, color=pole_color)
- plt.plot(zero_real, zero_imag, 'o', markersize=zero_markersize,
- color=zero_color)
- plt.xlabel('Real Axis')
- plt.ylabel('Imaginary Axis')
- plt.title(f'Poles and Zeros of ${latex(system)}$', pad=20)
- if grid:
- plt.grid()
- if show_axes:
- plt.axhline(0, color='black')
- plt.axvline(0, color='black')
- if show:
- plt.show()
- return
- return plt
- def step_response_numerical_data(system, prec=8, lower_limit=0,
- upper_limit=10, **kwargs):
- """
- Returns the numerical values of the points in the step response plot
- of a SISO continuous-time system. By default, adaptive sampling
- is used. If the user wants to instead get an uniformly
- sampled response, then ``adaptive`` kwarg should be passed ``False``
- and ``nb_of_points`` must be passed as additional kwargs.
- Refer to the parameters of class :class:`sympy.plotting.plot.LineOver1DRangeSeries`
- for more details.
- Parameters
- ==========
- system : SISOLinearTimeInvariant
- The system for which the unit step response data is to be computed.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- lower_limit : Number, optional
- The lower limit of the plot range. Defaults to 0.
- upper_limit : Number, optional
- The upper limit of the plot range. Defaults to 10.
- kwargs :
- Additional keyword arguments are passed to the underlying
- :class:`sympy.plotting.plot.LineOver1DRangeSeries` class.
- Returns
- =======
- tuple : (x, y)
- x = Time-axis values of the points in the step response. NumPy array.
- y = Amplitude-axis values of the points in the step response. NumPy array.
- Raises
- ======
- NotImplementedError
- When a SISO LTI system is not passed.
- When time delay terms are present in the system.
- ValueError
- When more than one free symbol is present in the system.
- The only variable in the transfer function should be
- the variable of the Laplace transform.
- When ``lower_limit`` parameter is less than 0.
- Examples
- ========
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import step_response_numerical_data
- >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s)
- >>> step_response_numerical_data(tf1) # doctest: +SKIP
- ([0.0, 0.025413462339411542, 0.0484508722725343, ... , 9.670250533855183, 9.844291913708725, 10.0],
- [0.0, 0.023844582399907256, 0.042894276802320226, ..., 6.828770759094287e-12, 6.456457160755703e-12])
- See Also
- ========
- step_response_plot
- """
- if lower_limit < 0:
- raise ValueError("Lower limit of time must be greater "
- "than or equal to zero.")
- _check_system(system)
- _x = Dummy("x")
- expr = system.to_expr()/(system.var)
- expr = apart(expr, system.var, full=True)
- _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec)
- return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit),
- **kwargs).get_points()
- def step_response_plot(system, color='b', prec=8, lower_limit=0,
- upper_limit=10, show_axes=False, grid=True, show=True, **kwargs):
- r"""
- Returns the unit step response of a continuous-time system. It is
- the response of the system when the input signal is a step function.
- Parameters
- ==========
- system : SISOLinearTimeInvariant type
- The LTI SISO system for which the Step Response is to be computed.
- color : str, tuple, optional
- The color of the line. Default is Blue.
- show : boolean, optional
- If ``True``, the plot will be displayed otherwise
- the equivalent matplotlib ``plot`` object will be returned.
- Defaults to True.
- lower_limit : Number, optional
- The lower limit of the plot range. Defaults to 0.
- upper_limit : Number, optional
- The upper limit of the plot range. Defaults to 10.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- show_axes : boolean, optional
- If ``True``, the coordinate axes will be shown. Defaults to False.
- grid : boolean, optional
- If ``True``, the plot will have a grid. Defaults to True.
- Examples
- ========
- .. plot::
- :context: close-figs
- :format: doctest
- :include-source: True
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import step_response_plot
- >>> tf1 = TransferFunction(8*s**2 + 18*s + 32, s**3 + 6*s**2 + 14*s + 24, s)
- >>> step_response_plot(tf1) # doctest: +SKIP
- See Also
- ========
- impulse_response_plot, ramp_response_plot
- References
- ==========
- .. [1] https://www.mathworks.com/help/control/ref/lti.step.html
- """
- x, y = step_response_numerical_data(system, prec=prec,
- lower_limit=lower_limit, upper_limit=upper_limit, **kwargs)
- plt.plot(x, y, color=color)
- plt.xlabel('Time (s)')
- plt.ylabel('Amplitude')
- plt.title(f'Unit Step Response of ${latex(system)}$', pad=20)
- if grid:
- plt.grid()
- if show_axes:
- plt.axhline(0, color='black')
- plt.axvline(0, color='black')
- if show:
- plt.show()
- return
- return plt
- def impulse_response_numerical_data(system, prec=8, lower_limit=0,
- upper_limit=10, **kwargs):
- """
- Returns the numerical values of the points in the impulse response plot
- of a SISO continuous-time system. By default, adaptive sampling
- is used. If the user wants to instead get an uniformly
- sampled response, then ``adaptive`` kwarg should be passed ``False``
- and ``nb_of_points`` must be passed as additional kwargs.
- Refer to the parameters of class :class:`sympy.plotting.plot.LineOver1DRangeSeries`
- for more details.
- Parameters
- ==========
- system : SISOLinearTimeInvariant
- The system for which the impulse response data is to be computed.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- lower_limit : Number, optional
- The lower limit of the plot range. Defaults to 0.
- upper_limit : Number, optional
- The upper limit of the plot range. Defaults to 10.
- kwargs :
- Additional keyword arguments are passed to the underlying
- :class:`sympy.plotting.plot.LineOver1DRangeSeries` class.
- Returns
- =======
- tuple : (x, y)
- x = Time-axis values of the points in the impulse response. NumPy array.
- y = Amplitude-axis values of the points in the impulse response. NumPy array.
- Raises
- ======
- NotImplementedError
- When a SISO LTI system is not passed.
- When time delay terms are present in the system.
- ValueError
- When more than one free symbol is present in the system.
- The only variable in the transfer function should be
- the variable of the Laplace transform.
- When ``lower_limit`` parameter is less than 0.
- Examples
- ========
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import impulse_response_numerical_data
- >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s)
- >>> impulse_response_numerical_data(tf1) # doctest: +SKIP
- ([0.0, 0.06616480200395854,... , 9.854500743565858, 10.0],
- [0.9999999799999999, 0.7042848373025861,...,7.170748906965121e-13, -5.1901263495547205e-12])
- See Also
- ========
- impulse_response_plot
- """
- if lower_limit < 0:
- raise ValueError("Lower limit of time must be greater "
- "than or equal to zero.")
- _check_system(system)
- _x = Dummy("x")
- expr = system.to_expr()
- expr = apart(expr, system.var, full=True)
- _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec)
- return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit),
- **kwargs).get_points()
- def impulse_response_plot(system, color='b', prec=8, lower_limit=0,
- upper_limit=10, show_axes=False, grid=True, show=True, **kwargs):
- r"""
- Returns the unit impulse response (Input is the Dirac-Delta Function) of a
- continuous-time system.
- Parameters
- ==========
- system : SISOLinearTimeInvariant type
- The LTI SISO system for which the Impulse Response is to be computed.
- color : str, tuple, optional
- The color of the line. Default is Blue.
- show : boolean, optional
- If ``True``, the plot will be displayed otherwise
- the equivalent matplotlib ``plot`` object will be returned.
- Defaults to True.
- lower_limit : Number, optional
- The lower limit of the plot range. Defaults to 0.
- upper_limit : Number, optional
- The upper limit of the plot range. Defaults to 10.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- show_axes : boolean, optional
- If ``True``, the coordinate axes will be shown. Defaults to False.
- grid : boolean, optional
- If ``True``, the plot will have a grid. Defaults to True.
- Examples
- ========
- .. plot::
- :context: close-figs
- :format: doctest
- :include-source: True
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import impulse_response_plot
- >>> tf1 = TransferFunction(8*s**2 + 18*s + 32, s**3 + 6*s**2 + 14*s + 24, s)
- >>> impulse_response_plot(tf1) # doctest: +SKIP
- See Also
- ========
- step_response_plot, ramp_response_plot
- References
- ==========
- .. [1] https://www.mathworks.com/help/control/ref/lti.impulse.html
- """
- x, y = impulse_response_numerical_data(system, prec=prec,
- lower_limit=lower_limit, upper_limit=upper_limit, **kwargs)
- plt.plot(x, y, color=color)
- plt.xlabel('Time (s)')
- plt.ylabel('Amplitude')
- plt.title(f'Impulse Response of ${latex(system)}$', pad=20)
- if grid:
- plt.grid()
- if show_axes:
- plt.axhline(0, color='black')
- plt.axvline(0, color='black')
- if show:
- plt.show()
- return
- return plt
- def ramp_response_numerical_data(system, slope=1, prec=8,
- lower_limit=0, upper_limit=10, **kwargs):
- """
- Returns the numerical values of the points in the ramp response plot
- of a SISO continuous-time system. By default, adaptive sampling
- is used. If the user wants to instead get an uniformly
- sampled response, then ``adaptive`` kwarg should be passed ``False``
- and ``nb_of_points`` must be passed as additional kwargs.
- Refer to the parameters of class :class:`sympy.plotting.plot.LineOver1DRangeSeries`
- for more details.
- Parameters
- ==========
- system : SISOLinearTimeInvariant
- The system for which the ramp response data is to be computed.
- slope : Number, optional
- The slope of the input ramp function. Defaults to 1.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- lower_limit : Number, optional
- The lower limit of the plot range. Defaults to 0.
- upper_limit : Number, optional
- The upper limit of the plot range. Defaults to 10.
- kwargs :
- Additional keyword arguments are passed to the underlying
- :class:`sympy.plotting.plot.LineOver1DRangeSeries` class.
- Returns
- =======
- tuple : (x, y)
- x = Time-axis values of the points in the ramp response plot. NumPy array.
- y = Amplitude-axis values of the points in the ramp response plot. NumPy array.
- Raises
- ======
- NotImplementedError
- When a SISO LTI system is not passed.
- When time delay terms are present in the system.
- ValueError
- When more than one free symbol is present in the system.
- The only variable in the transfer function should be
- the variable of the Laplace transform.
- When ``lower_limit`` parameter is less than 0.
- When ``slope`` is negative.
- Examples
- ========
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import ramp_response_numerical_data
- >>> tf1 = TransferFunction(s, s**2 + 5*s + 8, s)
- >>> ramp_response_numerical_data(tf1) # doctest: +SKIP
- (([0.0, 0.12166980856813935,..., 9.861246379582118, 10.0],
- [1.4504508011325967e-09, 0.006046440489058766,..., 0.12499999999568202, 0.12499999999661349]))
- See Also
- ========
- ramp_response_plot
- """
- if slope < 0:
- raise ValueError("Slope must be greater than or equal"
- " to zero.")
- if lower_limit < 0:
- raise ValueError("Lower limit of time must be greater "
- "than or equal to zero.")
- _check_system(system)
- _x = Dummy("x")
- expr = (slope*system.to_expr())/((system.var)**2)
- expr = apart(expr, system.var, full=True)
- _y = _fast_inverse_laplace(expr, system.var, _x).evalf(prec)
- return LineOver1DRangeSeries(_y, (_x, lower_limit, upper_limit),
- **kwargs).get_points()
- def ramp_response_plot(system, slope=1, color='b', prec=8, lower_limit=0,
- upper_limit=10, show_axes=False, grid=True, show=True, **kwargs):
- r"""
- Returns the ramp response of a continuous-time system.
- Ramp function is defined as the straight line
- passing through origin ($f(x) = mx$). The slope of
- the ramp function can be varied by the user and
- the default value is 1.
- Parameters
- ==========
- system : SISOLinearTimeInvariant type
- The LTI SISO system for which the Ramp Response is to be computed.
- slope : Number, optional
- The slope of the input ramp function. Defaults to 1.
- color : str, tuple, optional
- The color of the line. Default is Blue.
- show : boolean, optional
- If ``True``, the plot will be displayed otherwise
- the equivalent matplotlib ``plot`` object will be returned.
- Defaults to True.
- lower_limit : Number, optional
- The lower limit of the plot range. Defaults to 0.
- upper_limit : Number, optional
- The upper limit of the plot range. Defaults to 10.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- show_axes : boolean, optional
- If ``True``, the coordinate axes will be shown. Defaults to False.
- grid : boolean, optional
- If ``True``, the plot will have a grid. Defaults to True.
- Examples
- ========
- .. plot::
- :context: close-figs
- :format: doctest
- :include-source: True
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import ramp_response_plot
- >>> tf1 = TransferFunction(s, (s+4)*(s+8), s)
- >>> ramp_response_plot(tf1, upper_limit=2) # doctest: +SKIP
- See Also
- ========
- step_response_plot, ramp_response_plot
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Ramp_function
- """
- x, y = ramp_response_numerical_data(system, slope=slope, prec=prec,
- lower_limit=lower_limit, upper_limit=upper_limit, **kwargs)
- plt.plot(x, y, color=color)
- plt.xlabel('Time (s)')
- plt.ylabel('Amplitude')
- plt.title(f'Ramp Response of ${latex(system)}$ [Slope = {slope}]', pad=20)
- if grid:
- plt.grid()
- if show_axes:
- plt.axhline(0, color='black')
- plt.axvline(0, color='black')
- if show:
- plt.show()
- return
- return plt
- def bode_magnitude_numerical_data(system, initial_exp=-5, final_exp=5, freq_unit='rad/sec', **kwargs):
- """
- Returns the numerical data of the Bode magnitude plot of the system.
- It is internally used by ``bode_magnitude_plot`` to get the data
- for plotting Bode magnitude plot. Users can use this data to further
- analyse the dynamics of the system or plot using a different
- backend/plotting-module.
- Parameters
- ==========
- system : SISOLinearTimeInvariant
- The system for which the data is to be computed.
- initial_exp : Number, optional
- The initial exponent of 10 of the semilog plot. Defaults to -5.
- final_exp : Number, optional
- The final exponent of 10 of the semilog plot. Defaults to 5.
- freq_unit : string, optional
- User can choose between ``'rad/sec'`` (radians/second) and ``'Hz'`` (Hertz) as frequency units.
- Returns
- =======
- tuple : (x, y)
- x = x-axis values of the Bode magnitude plot.
- y = y-axis values of the Bode magnitude plot.
- Raises
- ======
- NotImplementedError
- When a SISO LTI system is not passed.
- When time delay terms are present in the system.
- ValueError
- When more than one free symbol is present in the system.
- The only variable in the transfer function should be
- the variable of the Laplace transform.
- When incorrect frequency units are given as input.
- Examples
- ========
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import bode_magnitude_numerical_data
- >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s)
- >>> bode_magnitude_numerical_data(tf1) # doctest: +SKIP
- ([1e-05, 1.5148378120533502e-05,..., 68437.36188804005, 100000.0],
- [-6.020599914256786, -6.0205999155219505,..., -193.4117304087953, -200.00000000260573])
- See Also
- ========
- bode_magnitude_plot, bode_phase_numerical_data
- """
- _check_system(system)
- expr = system.to_expr()
- freq_units = ('rad/sec', 'Hz')
- if freq_unit not in freq_units:
- raise ValueError('Only "rad/sec" and "Hz" are accepted frequency units.')
- _w = Dummy("w", real=True)
- if freq_unit == 'Hz':
- repl = I*_w*2*pi
- else:
- repl = I*_w
- w_expr = expr.subs({system.var: repl})
- mag = 20*log(Abs(w_expr), 10)
- x, y = LineOver1DRangeSeries(mag,
- (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points()
- return x, y
- def bode_magnitude_plot(system, initial_exp=-5, final_exp=5,
- color='b', show_axes=False, grid=True, show=True, freq_unit='rad/sec', **kwargs):
- r"""
- Returns the Bode magnitude plot of a continuous-time system.
- See ``bode_plot`` for all the parameters.
- """
- x, y = bode_magnitude_numerical_data(system, initial_exp=initial_exp,
- final_exp=final_exp, freq_unit=freq_unit)
- plt.plot(x, y, color=color, **kwargs)
- plt.xscale('log')
- plt.xlabel('Frequency (%s) [Log Scale]' % freq_unit)
- plt.ylabel('Magnitude (dB)')
- plt.title(f'Bode Plot (Magnitude) of ${latex(system)}$', pad=20)
- if grid:
- plt.grid(True)
- if show_axes:
- plt.axhline(0, color='black')
- plt.axvline(0, color='black')
- if show:
- plt.show()
- return
- return plt
- def bode_phase_numerical_data(system, initial_exp=-5, final_exp=5, freq_unit='rad/sec', phase_unit='rad', **kwargs):
- """
- Returns the numerical data of the Bode phase plot of the system.
- It is internally used by ``bode_phase_plot`` to get the data
- for plotting Bode phase plot. Users can use this data to further
- analyse the dynamics of the system or plot using a different
- backend/plotting-module.
- Parameters
- ==========
- system : SISOLinearTimeInvariant
- The system for which the Bode phase plot data is to be computed.
- initial_exp : Number, optional
- The initial exponent of 10 of the semilog plot. Defaults to -5.
- final_exp : Number, optional
- The final exponent of 10 of the semilog plot. Defaults to 5.
- freq_unit : string, optional
- User can choose between ``'rad/sec'`` (radians/second) and '``'Hz'`` (Hertz) as frequency units.
- phase_unit : string, optional
- User can choose between ``'rad'`` (radians) and ``'deg'`` (degree) as phase units.
- Returns
- =======
- tuple : (x, y)
- x = x-axis values of the Bode phase plot.
- y = y-axis values of the Bode phase plot.
- Raises
- ======
- NotImplementedError
- When a SISO LTI system is not passed.
- When time delay terms are present in the system.
- ValueError
- When more than one free symbol is present in the system.
- The only variable in the transfer function should be
- the variable of the Laplace transform.
- When incorrect frequency or phase units are given as input.
- Examples
- ========
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import bode_phase_numerical_data
- >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s)
- >>> bode_phase_numerical_data(tf1) # doctest: +SKIP
- ([1e-05, 1.4472354033813751e-05, 2.035581932165858e-05,..., 47577.3248186011, 67884.09326036123, 100000.0],
- [-2.5000000000291665e-05, -3.6180885085e-05, -5.08895483066e-05,...,-3.1415085799262523, -3.14155265358979])
- See Also
- ========
- bode_magnitude_plot, bode_phase_numerical_data
- """
- _check_system(system)
- expr = system.to_expr()
- freq_units = ('rad/sec', 'Hz')
- phase_units = ('rad', 'deg')
- if freq_unit not in freq_units:
- raise ValueError('Only "rad/sec" and "Hz" are accepted frequency units.')
- if phase_unit not in phase_units:
- raise ValueError('Only "rad" and "deg" are accepted phase units.')
- _w = Dummy("w", real=True)
- if freq_unit == 'Hz':
- repl = I*_w*2*pi
- else:
- repl = I*_w
- w_expr = expr.subs({system.var: repl})
- if phase_unit == 'deg':
- phase = arg(w_expr)*180/pi
- else:
- phase = arg(w_expr)
- x, y = LineOver1DRangeSeries(phase,
- (_w, 10**initial_exp, 10**final_exp), xscale='log', **kwargs).get_points()
- return x, y
- def bode_phase_plot(system, initial_exp=-5, final_exp=5,
- color='b', show_axes=False, grid=True, show=True, freq_unit='rad/sec', phase_unit='rad', **kwargs):
- r"""
- Returns the Bode phase plot of a continuous-time system.
- See ``bode_plot`` for all the parameters.
- """
- x, y = bode_phase_numerical_data(system, initial_exp=initial_exp,
- final_exp=final_exp, freq_unit=freq_unit, phase_unit=phase_unit)
- plt.plot(x, y, color=color, **kwargs)
- plt.xscale('log')
- plt.xlabel('Frequency (%s) [Log Scale]' % freq_unit)
- plt.ylabel('Phase (%s)' % phase_unit)
- plt.title(f'Bode Plot (Phase) of ${latex(system)}$', pad=20)
- if grid:
- plt.grid(True)
- if show_axes:
- plt.axhline(0, color='black')
- plt.axvline(0, color='black')
- if show:
- plt.show()
- return
- return plt
- def bode_plot(system, initial_exp=-5, final_exp=5,
- grid=True, show_axes=False, show=True, freq_unit='rad/sec', phase_unit='rad', **kwargs):
- r"""
- Returns the Bode phase and magnitude plots of a continuous-time system.
- Parameters
- ==========
- system : SISOLinearTimeInvariant type
- The LTI SISO system for which the Bode Plot is to be computed.
- initial_exp : Number, optional
- The initial exponent of 10 of the semilog plot. Defaults to -5.
- final_exp : Number, optional
- The final exponent of 10 of the semilog plot. Defaults to 5.
- show : boolean, optional
- If ``True``, the plot will be displayed otherwise
- the equivalent matplotlib ``plot`` object will be returned.
- Defaults to True.
- prec : int, optional
- The decimal point precision for the point coordinate values.
- Defaults to 8.
- grid : boolean, optional
- If ``True``, the plot will have a grid. Defaults to True.
- show_axes : boolean, optional
- If ``True``, the coordinate axes will be shown. Defaults to False.
- freq_unit : string, optional
- User can choose between ``'rad/sec'`` (radians/second) and ``'Hz'`` (Hertz) as frequency units.
- phase_unit : string, optional
- User can choose between ``'rad'`` (radians) and ``'deg'`` (degree) as phase units.
- Examples
- ========
- .. plot::
- :context: close-figs
- :format: doctest
- :include-source: True
- >>> from sympy.abc import s
- >>> from sympy.physics.control.lti import TransferFunction
- >>> from sympy.physics.control.control_plots import bode_plot
- >>> tf1 = TransferFunction(1*s**2 + 0.1*s + 7.5, 1*s**4 + 0.12*s**3 + 9*s**2, s)
- >>> bode_plot(tf1, initial_exp=0.2, final_exp=0.7) # doctest: +SKIP
- See Also
- ========
- bode_magnitude_plot, bode_phase_plot
- """
- plt.subplot(211)
- mag = bode_magnitude_plot(system, initial_exp=initial_exp, final_exp=final_exp,
- show=False, grid=grid, show_axes=show_axes,
- freq_unit=freq_unit, **kwargs)
- mag.title(f'Bode Plot of ${latex(system)}$', pad=20)
- mag.xlabel(None)
- plt.subplot(212)
- bode_phase_plot(system, initial_exp=initial_exp, final_exp=final_exp,
- show=False, grid=grid, show_axes=show_axes, freq_unit=freq_unit, phase_unit=phase_unit, **kwargs).title(None)
- if show:
- plt.show()
- return
- return plt
|