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

Describing functions#521

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

Conversation

murrayrm
Copy link
Member

@murrayrmmurrayrm commentedJan 28, 2021
edited
Loading

This PR adds a basic describing function capability, mainly captured in the newdescribing_function_plot command. Almost all of the new code is in thedescfcn.py file, with a few changes elsewhere to support some of the patterns. There is a Jupyter notebook demonstrating the use of the code.

Additional changes:

  • Added amirror keyword tonyquist_plot that sets the line style used for plotting the complementary portion of the Nyquist plot. This defaults to--, which matches the FBS convention.
  • Added an_isstatic method to theNonlinearIOSystem class that allows passing a nonlinear I/O object as the argument for computing describing functions.
  • Added a__call__ method to theNonlinearIOSystem class that direct calling of a static nonlinear input/output function (returning the value of the function at the given input).

@coveralls
Copy link

coveralls commentedJan 28, 2021
edited
Loading

Coverage Status

Coverage increased (+0.3%) to 88.024% when pulling93d97cc on murrayrm:describing_functions intod75c395 on python-control:master.

sawyerbfuller added a commit to sawyerbfuller/python-control that referenced this pull requestJan 28, 2021
@sawyerbfullersawyerbfuller mentioned this pull requestJan 28, 2021
@@ -450,6 +450,10 @@ def issiso(self):
"""Check to see if a system is single input, single output"""
return self.ninputs == 1 and self.noutputs == 1

def isstatic(self):
Copy link
Contributor

@sawyerbfullersawyerbfullerJan 28, 2021
edited
Loading

Choose a reason for hiding this comment

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

statespace and xferfcn have methodsis_static_gain() I added. should we choose a standard conventiuon? I would lean towardisstatic() because it is shorter and matchesissiso(),

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I agree we should be uniform. I'm not sure thatis_static_gain() would make sense for a nonlinear system, so that is another reason to opt forisstatic().

Also: these are mainly internally used functions, so I think it is OK to stick withisstatic/issiso rather thanis_static/is_siso.

sawyerbfuller reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Converted to_isstatic in latest push since this really is an internal function, not intended to be seen by the user.

@sawyerbfuller: when we updateis_static_gain, we can either change to_isstatic if all usage is internal or change toisstatic (and updatedescfcn.py for consistency).

Copy link
Contributor

Choose a reason for hiding this comment

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

renamed in#524

Copy link
Contributor

@roryyorkeroryyorke left a comment

Choose a reason for hiding this comment

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

I'll try to review fromdescribing_function_plot on later this weekend.


# Class for nonlinearities with a built-in describing function
class DescribingFunctionNonlinearity():
"""Base class for nonlinear functions with a describing function
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"""Baseclassfornonlinearfunctionswithadescribingfunction
"""Baseclassfornonlinearsystemswithadescribingfunction

"""Base class for nonlinear functions with a describing function

This class is intended to be used as a base class for nonlinear functions
that have a analytically defined describing function (accessed via the
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
thathaveaanalyticallydefineddescribingfunction (accessedviathe
thathaveananalyticallydefineddescribingfunction (accessedviathe

#

"""The :mod:~control.descfcn` module contains function for performing
closed loop analysis of systems with static nonlinearities using
Copy link
Contributor

Choose a reason for hiding this comment

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

I find thestatic terminology a little confusing, especially given that some of the describing functions declared here, e.g. hysteresis, returnFalse in their_isstatic methods. Maybe "non-linearities with frequency-independent describing functions", though that is rather long-winded.

implement a `call` method that evaluates the nonlinearity at a given point
and an `_isstatic` method that is `True` if the nonlinearity has no
internal state.

Copy link
Contributor

Choose a reason for hiding this comment

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

How about: "Subclasses should override thedescribing_function,__call__, and_isstatic methods."

It looks like__call__ 's inputs and outputsmust be scalar, since__call__ is allowed to change instance memory, so the sequence of calls matters.

Candescribing_function be called with anumpy.ndarray of amplitude values? This seems like a reasonable thing to want to do.

If I understand correctly, if__call__ updates any instance state, then_isstatic must returnFalse -- it would be good to mention that too.

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 updated the documentation a bit to address the comments above.

In terms of callingDescribingFunctionNonlinearity.description_function with an array: the intent is that this is a low level function that just evaluates the describing function at a scalar value. Thedescribing_function function has the code to evaluate on an array.


"""
# If there is an analytical solution, trying using that first
if try_method and hasattr(F, 'describing_function'):
Copy link
Contributor

Choose a reason for hiding this comment

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

thehasattr check will succeed for any base class ofDescribingFunctionNonlinearity - shouldn't you also catch theNotImplementedError ?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Good catch. Fixed.

#

# Evaluate over a full range of angles
theta = np.linspace(0, 2*np.pi, num_points)
Copy link
Contributor

Choose a reason for hiding this comment

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

this is not something I'm not at all sure about: should this omit the final point, so it's more like a discrete Fourier transform?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Good catch. We are approximating the integral by a discrete sum with the assumption that the function is constant on each small interval. But this means that we don't want to include the value at 2pi (since we already captured the segment just before 2pi in the previous evaluation).

# Save the scaling factor for to make the formulas simpler
scale = dtheta / np.pi / a

# Evaluate the function (twice) along a sinusoid (for internal state)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see how this is evaluating the function twice over the sinusoid (assume that means two periods of sinusoid).

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Left over from an earlier iteration (before I implemented_isstatic). Fixed.

#
# N(A) = M_1(A) e^{j \phi_1(A)} / A
#
# To compute this, we compute F(\theta) for \theta between 0 and 2 \pi,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# To compute this, we compute F(\theta) for \theta between 0 and 2 \pi,
# To compute this, we compute F(A\sin\theta) for \theta between 0 and 2 \pi,

#
# and then integrate the product against \sin\theta and \cos\theta to obtain
#
# \int_0^{2\pi} F(a\sin\theta) \sin\theta d\theta = M_1 \pi \cos\phi
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# \int_0^{2\pi} F(a\sin\theta) \sin\theta d\theta = M_1 \pi \cos\phi
# \int_0^{2\pi} F(A\sin\theta) \sin\theta d\theta = M_1 \pi \cos\phi

# and then integrate the product against \sin\theta and \cos\theta to obtain
#
# \int_0^{2\pi} F(a\sin\theta) \sin\theta d\theta = M_1 \pi \cos\phi
# \int_0^{2\pi} F(a\sin\theta) \cos\theta d\theta = M_1 \pi \sin\phi
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# \int_0^{2\pi} F(a\sin\theta) \cos\theta d\theta = M_1 \pi \sin\phi
# \int_0^{2\pi} F(A\sin\theta) \cos\theta d\theta = M_1 \pi \sin\phi

Copy link
Contributor

@roryyorkeroryyorke left a comment

Choose a reason for hiding this comment

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

Searching for the intersections will be O(m * n).

We could look libraries we can pull in to find the intersection of H(jw) and N(A). I googled "numpy polyline intersection" and found pointers tohttps://pypi.org/project/Shapely/, but that may be over the top.

We could also look at usingscipy.optimize.root, though I don't see an obvious way to bound the domain of search. And we'd have to handle the multiple roots ourselves.

When N(A) is strictly real (saturation, deadzone, relay), we could probably specialize the solver.

self.ub = ub

def __call__(self, x):
return np.maximum(self.lb, np.minimum(x, self.ub))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
returnnp.maximum(self.lb,np.minimum(x,self.ub))
returnnp.clip(x,self.lb,self.ub)

@@ -508,7 +508,7 @@ def gen_zero_centered_series(val_min, val_max, period):

def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
arrowhead_length=0.1, arrowhead_width=0.1,
color=None, *args, **kwargs):
mirror='--',color=None, *args, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

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

This new argument should be documented. Are we going to allowNone for no mirror? Below you checked forFalse.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Updated documentation. Should either be a linestyle or False if you don't want to plot the mirror image at all. Setting it to (explicitly) to None will generate an error (from matplotlib).

if self.lb <= A and A <= self.ub:
return 1.
else:
alpha, beta = math.asin(self.ub/A), math.asin(-self.lb/A)
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, the expression in Slotine & Li, and alsohttps://ocw.mit.edu/courses/aeronautics-and-astronautics/16-30-estimation-and-control-of-aerospace-systems-spring-2004/readings/gelb_ch2_ocr.pdf andhttps://ocw.mit.edu/resources/res-6-010-electronic-feedback-systems-spring-2013/textbook/MITRES_6-010S13_chap06.pdf are different to what you have here, but they may be equivalent. All these references restrict themselves to saturation with odd symmetry, though.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

It's equivalent (I checked, but also the unit tests check this).


# Backlash nonlinearity (#48 in Gelb and Vander Velde, 1968)
class backlash_nonlinearity(DescribingFunctionNonlinearity):
"""Backlash nonlinearity for use in describing function analysis
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I saw that, but couldn't figure out what the "friction-controlled" had to do with it exactly. It seemed to me that you just had backlash as in a gear and so the output just followed the input (modulo the backlash action).

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's not important, but I remember a colleague always talking about the two backlash types (friction-controlled and inertia-controlled), and I thought it was standard nomenclature. The inertia-controlled case is discussed in the reference above immediately after friction-controlled.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Sorry, I was looking at something different (probably just the table in Appendix B, which didn't have the explanation). I'll update.

# Convert input to an array
x_array = np.array(x)

y = []
Copy link
Contributor

Choose a reason for hiding this comment

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

there's an inconsistency here: you've allowed a vector input for this__call__, but not the others.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Good catch.



# Static function via a class
class saturation_class():
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
classsaturation_class():
classsaturation_class:

np.testing.assert_array_equal(satsys([0.]), [0.])

# Test SIMO nonlinearity
def _simofcn(t, x, u, params={}):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def_simofcn(t,x,u,params={}):
def_simofcn(t,x,u,params):

return np.array([np.sin(u[0]) * np.cos(u[1])])
miso_sys = ct.NonlinearIOSystem(None, outfcn=_misofcn, input=2, output=1)
np.testing.assert_array_equal(miso_sys([0, 0]), [0])
np.testing.assert_array_equal(miso_sys([0, 0]), [0])
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
np.testing.assert_array_equal(miso_sys([0, 0]), [0])



# Test saturation describing function in multiple ways
def test_saturation_describing_function(satsys):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you mean to test with the functionsaturation here, but this testssaturation_class twice.

linear system and a static nonlinearity, it is possible to obtain a
generalization of Nyquist's stability criterion based on the idea of
describing functions. The basic concept involves approximating the
response of a static nonlinearity to an input :math:`u = A e^{\omega
Copy link
Contributor

Choose a reason for hiding this comment

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

e^{j\omega t} ?

@murrayrmmurrayrmforce-pushed thedescribing_functions branch 3 times, most recently fromaba5b43 tof5bca43CompareJanuary 31, 2021 18:50
@murrayrmmurrayrm mentioned this pull requestFeb 6, 2021
@murrayrmmurrayrm merged commitc04431f intopython-control:masterFeb 7, 2021
@murrayrmmurrayrm deleted the describing_functions branchFebruary 8, 2021 00:25
@murrayrmmurrayrm added this to the0.9.0 milestoneMar 20, 2021
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@roryyorkeroryyorkeroryyorke left review comments

@sawyerbfullersawyerbfullersawyerbfuller left review comments

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
0.9.0
Development

Successfully merging this pull request may close these issues.

4 participants
@murrayrm@coveralls@roryyorke@sawyerbfuller

[8]ページ先頭

©2009-2025 Movatter.jp