Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Frequency plot improvements#1011

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
18 commits
Select commitHold shift + click to select a range
b9acc99
allow label keyword in bode_plot() to override default labels
murrayrmJun 1, 2024
0b6348f
add label keyword to singular_value_plot and nyquist_plot
murrayrmJun 1, 2024
606cf48
add additional tests for freqplot label override + small fixes
murrayrmJun 2, 2024
5e405cc
don't strip common parts of labels if given explicitly
murrayrmJun 4, 2024
eeebec3
BUG: indent_radius not handled properly when passed to nyquist_plot
murrayrmJun 6, 2024
402b45f
restore [wmin, wmax] functionality + documentation updates
murrayrmJun 9, 2024
6f6c70d
refactoring/regularization of ax keyword processing
murrayrmJun 14, 2024
404fbdf
regularize processing of rcParams
murrayrmJun 14, 2024
1a94f4e
add suptitle() function for better centered titles
murrayrmJun 15, 2024
28995f1
updated freqplot documentation + figures
murrayrmJun 15, 2024
ffb0a0f
move plotutil to ctrlplot
murrayrmJun 15, 2024
4734912
update freq_label handling + add Bode/Nyquist notebook
murrayrmJun 15, 2024
aeb187f
set up frd() as full factory function for FRD I/Osystems
murrayrmJun 16, 2024
ddb5b7c
update unexpected keyword check for GitHub action variation
murrayrmJun 16, 2024
2fde963
allowed Bode/Nyquist for mixed FRD systems
murrayrmJun 16, 2024
b7c158b
update CDS 110 example
murrayrmJun 26, 2024
59a676e
fix typos in doc, docstrings pointed out by @slivingston review
murrayrmJun 29, 2024
343df2c
fix issues in examples/cds110_bode-nyquist.ipynb per @slivingston review
murrayrmJun 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletionLICENSE
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
Copyright (c) 2009-2016 by California Institute of Technology
Copyright (c) 2016-2023 by python-control developers
Copyright (c) 2012 by Delft University of Technology
Copyright (c) 2016-2024 by python-control developers
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
1 change: 1 addition & 0 deletionscontrol/__init__.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -83,6 +83,7 @@
from .timeplot import *

from .bdalg import *
from .ctrlplot import *
from .delay import *
from .descfcn import *
from .dtime import *
Expand Down
4 changes: 2 additions & 2 deletionscontrol/bdalg.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -279,8 +279,8 @@ def feedback(sys1, sys2=1, sign=-1):
if isinstance(sys2, (int, float, complex, np.number, np.ndarray,
tf.TransferFunction)):
sys1 = tf._convert_to_transfer_function(sys1)
elif isinstance(sys2, frd.FRD):
sys1 = frd._convert_to_FRD(sys1, sys2.omega)
elif isinstance(sys2, frd.FrequencyResponseData):
sys1 = frd._convert_to_frd(sys1, sys2.omega)
else:
sys1 = ss._convert_to_statespace(sys1)

Expand Down
74 changes: 74 additions & 0 deletionscontrol/ctrlplot.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
# ctrlplot.py - utility functions for plotting
# Richard M. Murray, 14 Jun 2024
#
# Collection of functions that are used by various plotting functions.

import matplotlib.pyplot as plt
import numpy as np

from . import config

__all__ = ['suptitle']


def suptitle(
title, fig=None, frame='axes', **kwargs):
"""Add a centered title to a figure.

This is a wrapper for the matplotlib `suptitle` function, but by
setting ``frame`` to 'axes' (default) then the title is centered on the
midpoint of the axes in the figure, rather than the center of the
figure. This usually looks better (particularly with multi-panel
plots), though it takes longer to render.

Parameters
----------
title : str
Title text.
fig : Figure, optional
Matplotlib figure. Defaults to current figure.
frame : str, optional
Coordinate frame to use for centering: 'axes' (default) or 'figure'.
**kwargs : :func:`matplotlib.pyplot.suptitle` keywords, optional
Additional keywords (passed to matplotlib).

"""
rcParams = config._get_param('freqplot', 'rcParams', kwargs, pop=True)

if fig is None:
fig = plt.gcf()

if frame == 'figure':
with plt.rc_context(rcParams):
fig.suptitle(title, **kwargs)

elif frame == 'axes':
# TODO: move common plotting params to 'ctrlplot'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This same TODO comment is already freqplot.py, and it is not clear what the comment means here (in ctrlplot). Should the TODO comment be deleted here in favor of the one in freqplot.py ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Reading it again, I think this TODO comment is to be able to

rcParams=config._get_param('ctrlplot','rcParams',rcParams)

If so, then I understand the motivation, and OK to keep both TODO comments.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've got another PR coming that will address both TODOs. Doing things in stages so that everything is in (slightly) smaller chunks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Awesome. I am almost done reviewing this one.

rcParams = config._get_param('freqplot', 'rcParams', rcParams)
with plt.rc_context(rcParams):
plt.tight_layout() # Put the figure into proper layout
xc, _ = _find_axes_center(fig, fig.get_axes())

fig.suptitle(title, x=xc, **kwargs)
plt.tight_layout() # Update the layout

else:
raise ValueError(f"unknown frame '{frame}'")


def _find_axes_center(fig, axs):
"""Find the midpoint between axes in display coordinates.

This function finds the middle of a plot as defined by a set of axes.

"""
inv_transform = fig.transFigure.inverted()
xlim = ylim = [1, 0]
for ax in axs:
ll = inv_transform.transform(ax.transAxes.transform((0, 0)))
ur = inv_transform.transform(ax.transAxes.transform((1, 1)))

xlim = [min(ll[0], xlim[0]), max(ur[0], xlim[1])]
ylim = [min(ll[1], ylim[0]), max(ur[1], ylim[1])]

return (np.sum(xlim)/2, np.sum(ylim)/2)
148 changes: 67 additions & 81 deletionscontrol/frdata.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,27 @@
# Copyright (c) 2010 by California Institute of Technology
# Copyright (c) 2012 by Delft University of Technology
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the names of the California Institute of Technology nor
# the Delft University of Technology nor
# the names of its contributors may be used to endorse or promote
# products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
# frdata.py - frequency response data representation and functions
#
# Author: M.M. (Rene) van Paassen (using xferfcn.py as basis)
# Date: 02 Oct 12


"""
Frequency response data representation and functions.

This module contains the FRD class and also functions that operate on
FRD data.
"""

# External function declarations
from copy import copy
from warnings import warn

import numpy as np
from numpy import angle, array, empty, ones, \
real,imag, absolute, eye, linalg,where, sort
from scipy.interpolate importsplprep, splev
from numpy importabsolute,angle, array, empty, eye, imag, linalg, ones, \
real,sort,where
from scipy.interpolate importsplev, splprep

from .lti importLTI, _process_frequency_response
from . importconfig
from .exception import pandas_check
from .iosys import InputOutputSystem, _process_iosys_keywords, common_timebase
from . importconfig
from .lti importLTI, _process_frequency_response

__all__ = ['FrequencyResponseData', 'FRD', 'frd']

Expand DownExpand Up@@ -100,6 +66,10 @@ class constructor, using the :func:~~control.frd` factory function
dt : float, True, or None
System timebase.

See Also
--------
frd

Notes
-----
The main data members are 'omega' and 'fresp', where 'omega' is a 1D array
Expand All@@ -120,7 +90,6 @@ class constructor, using the :func:~~control.frd` factory function
for a more detailed description.

"""

#
# Class attributes
#
Expand DownExpand Up@@ -206,11 +175,12 @@ def __init__(self, *args, **kwargs):
"Needs 1 or 2 arguments; received %i." % len(args))

#
# Processkey word arguments
# Processkeyword arguments
#

# If data was generated by a system, keep track of that
self.sysname = kwargs.pop('sysname', None)
# If data was generated by a system, keep track of that (used when
# plotting data). Otherwise, use the system name, if given.
self.sysname = kwargs.pop('sysname', kwargs.get('name', None))

# Keep track of default properties for plotting
self.plot_phase = kwargs.pop('plot_phase', None)
Expand DownExpand Up@@ -280,7 +250,7 @@ def __str__(self):
"""String representation of the transfer function."""

mimo = self.ninputs > 1 or self.noutputs > 1
outstr = ['Frequency response data']
outstr = [f"{InputOutputSystem.__str__(self)}"]

for i in range(self.ninputs):
for j in range(self.noutputs):
Expand DownExpand Up@@ -322,7 +292,7 @@ def __add__(self, other):

# Convert the second argument to a frequency response function.
# or re-base the frd to the current omega (if needed)
other =_convert_to_FRD(other, omega=self.omega)
other =_convert_to_frd(other, omega=self.omega)

# Check that the input-output sizes are consistent.
if self.ninputs != other.ninputs:
Expand DownExpand Up@@ -359,7 +329,7 @@ def __mul__(self, other):
return FRD(self.fresp * other, self.omega,
smooth=(self.ifunc is not None))
else:
other =_convert_to_FRD(other, omega=self.omega)
other =_convert_to_frd(other, omega=self.omega)

# Check that the input-output sizes are consistent.
if self.ninputs != other.noutputs:
Expand All@@ -386,7 +356,7 @@ def __rmul__(self, other):
return FRD(self.fresp * other, self.omega,
smooth=(self.ifunc is not None))
else:
other =_convert_to_FRD(other, omega=self.omega)
other =_convert_to_frd(other, omega=self.omega)

# Check that the input-output sizes are consistent.
if self.noutputs != other.ninputs:
Expand DownExpand Up@@ -414,7 +384,7 @@ def __truediv__(self, other):
return FRD(self.fresp * (1/other), self.omega,
smooth=(self.ifunc is not None))
else:
other =_convert_to_FRD(other, omega=self.omega)
other =_convert_to_frd(other, omega=self.omega)

if (self.ninputs > 1 or self.noutputs > 1 or
other.ninputs > 1 or other.noutputs > 1):
Expand All@@ -433,7 +403,7 @@ def __rtruediv__(self, other):
return FRD(other / self.fresp, self.omega,
smooth=(self.ifunc is not None))
else:
other =_convert_to_FRD(other, omega=self.omega)
other =_convert_to_frd(other, omega=self.omega)

if (self.ninputs > 1 or self.noutputs > 1 or
other.ninputs > 1 or other.noutputs > 1):
Expand DownExpand Up@@ -572,8 +542,8 @@ def __call__(self, s=None, squeeze=None, return_magphase=None):
------
ValueError
If `s` is not purely imaginary, because
:class:`FrequencyDomainData` systems are only defined at imaginary
frequency values.
:class:`FrequencyResponseData` systems are only defined at
imaginary values (corresponding to real frequencies).

"""
if s is None:
Expand DownExpand Up@@ -638,7 +608,7 @@ def freqresp(self, omega):
def feedback(self, other=1, sign=-1):
"""Feedback interconnection between two FRD objects."""

other =_convert_to_FRD(other, omega=self.omega)
other =_convert_to_frd(other, omega=self.omega)

if (self.noutputs != other.ninputs or self.ninputs != other.noutputs):
raise ValueError(
Expand DownExpand Up@@ -710,7 +680,7 @@ def to_pandas(self):
FRD = FrequencyResponseData


def_convert_to_FRD(sys, omega, inputs=1, outputs=1):
def_convert_to_frd(sys, omega, inputs=1, outputs=1):
"""Convert a system to frequency response data form (if needed).

If sys is already an frd, and its frequency range matches or
Expand All@@ -721,14 +691,14 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1):
manually, as in:

>>> import numpy as np
>>> from control.frdata import_convert_to_FRD
>>> from control.frdata import_convert_to_frd

>>> omega = np.logspace(-1, 1)
>>> frd =_convert_to_FRD(3., omega) # Assumes inputs = outputs = 1
>>> frd =_convert_to_frd(3., omega) # Assumes inputs = outputs = 1
>>> frd.ninputs, frd.noutputs
(1, 1)

>>> frd =_convert_to_FRD(1., omega, inputs=3, outputs=2)
>>> frd =_convert_to_frd(1., omega, inputs=3, outputs=2)
>>> frd.ninputs, frd.noutputs
(3, 2)

Expand DownExpand Up@@ -777,51 +747,67 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1):
sys.__class__)


def frd(*args):
"""frd(d, w)

Construct a frequency response data model.
def frd(*args, **kwargs):
"""frd(response, omega[, dt])

frd models store the (measured)frequency responseof a system.
Construct afrequency responsedata (FRD) model.

This function can be called in different ways:
A frequency response data model stores the (measured) frequency response
of a system. This factory function can be called in different ways:

``frd(response,freqs)``
``frd(response,omega)``
Create an frd model with the given response data, in the form of
complex response vector, at matchingfrequency freqs [in rad/s]
complex response vector, at matchingfrequencies ``omega`` [in rad/s].

``frd(sys,freqs)``
``frd(sys,omega)``
Convert an LTI system into an frd model with data at frequencies
freqs.
``omega``.

Parameters
----------
response: array_like, or list
complex vector with the system response
freq: array_lik or lis
vector with frequencies
sys: LTI (StateSpace or TransferFunction)
A linear system
response : array_like or LTI system
Complex vector with the system response or an LTI system that can
be used to copmute the frequency response at a list of frequencies.
omega : array_like
Vector of frequencies at which the response is evaluated.
dt : float, True, or None
System timebase.
smooth : bool, optional
If ``True``, create an interpolation function that allows the
frequency response to be computed at any frequency within the range
of frequencies give in ``omega``. If ``False`` (default),
frequency response can only be obtained at the frequencies
specified in ``omega``.

Returns
-------
sys: FRD
New frequency response system
sys : :class:`FrequencyResponseData`
New frequency response data system.

Other Parameters
----------------
inputs, outputs : str, or list of str, optional
List of strings that name the individual signals of the transformed
system. If not given, the inputs and outputs are the same as the
original system.
name : string, optional
System name. If unspecified, a generic name <sys[id]> is generated
with a unique integer id.

See Also
--------
FRD, ss, tf
FrequencyResponseData, frequency_response, ss, tf

Examples
--------
>>> # Create from measurements
>>> response = [1.0, 1.0, 0.5]
>>>freqs = [1, 10, 100]
>>> F = ct.frd(response,freqs)
>>>omega = [1, 10, 100]
>>> F = ct.frd(response,omega)

>>> G = ct.tf([1], [1, 1])
>>>freqs = [1, 10, 100]
>>> F = ct.frd(G,freqs)
>>>omega = [1, 10, 100]
>>> F = ct.frd(G,omega)

"""
returnFRD(*args)
returnFrequencyResponseData(*args, **kwargs)
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp