GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — new dynamics() and output() methods in StateSpace by sawyerbfuller · Pull Request #546 · python-control/python-control

Conversation

@sawyerbfuller

@sawyerbfuller sawyerbfuller commented Feb 16, 2021

Copy link
Copy Markdown
Contributor

Creates new dynamics(x, u) and output(x, u) methods for StateSpace systems. These correspond to the right hand side of the dynamcs and output equations for such systems (i.e. calculates xdot in xdot = Ax+Bu and y in y=Cx+Bu). This can be used for simulating or numerically integrating such systems in your own code, as suggested in #83.

Renames equivalent private methods _rhs and _out functions in iosys to same name. The latter have a slightly different call signature: the first argument is the time t rather than the state x. Since they were private methods, we can change them; it might make sense to rearrange arguments so that t is a keyword argument that appears at the end with a default value (e.g. 0) so that call signatures then are the same across system types.

Follows the discussion in #434.

I considered a few alternatives for the name dynamics: eval, evaluate, update, evolve, evolve_state, calculate, and compute. I settled on dynamics because it works for both cont-time and discrete-time systems, suggests that it is evaluating how they change in time, and doesn't suggest any mutation is happening to the system, unlike update. Happy to consider alternatives.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
sawyerbfuller added 3 commits February 16, 2021 12:24
@sawyerbfuller
added methods dynamics and output to StateSpace
f701b75
@sawyerbfuller
rename method _rhs in iosys to dynamics
033b236
@sawyerbfuller
rename _out methods in iosys to output
dd681e2
@coveralls

coveralls commented Feb 16, 2021

Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.008%) to 88.791% when pulling dd681e2 on sawyerbfuller:dynamics into 9c6c619 on python-control:master.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ilayn

ilayn commented Feb 16, 2021

Copy link
Copy Markdown

Following #83 I don't see why anyone won't write

def axplusbu(x, u): return A @ x + B @ u

Instead what you might want to do is to provide a callable so that people can plug it in wherever they want with all bells and whistles included. In english, you return a function which is built out of the parts of the IOsystem and that is independent from the model itself. Otherwise this doesn't seem like doing anything too useful.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@murrayrm

murrayrm commented Feb 17, 2021

Copy link
Copy Markdown
Member

What happens if you have a LinearIOSystem, which is both an InputOutputSystem and a StateSpace system? Should the call signature be sys.dynamics(t, x, u) or sys.dynamics(x, u)?

Also, what happens if a system has parameters (allowed for I/O systems)? Should I be able to set those parameter values when I call the (public) dynamics function? If not, how do I change parameter values?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sawyerbfuller

sawyerbfuller commented Feb 17, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

@ilayn I agree, it's not too onerous to write that out. I added these because I have been playing around with such an integrator and it does seem to clean up/improve code appearance to be able to write x[k+1] = P.dynamics(x[k], u[k]) than to write out all of the matrices (in my code, I'm working with a number of systems at once and spelling out all of their respective A, B, C, D matrices becomes unwieldy). You can also use it in scipy.solve_ivp with something like scipy.solve_ivp(lambda t, x: P.dynamics(x, -K@x), Tfinal) (Note: I might have the syntax on that slightly wrong).

can you elaborate more on what you had in mind?

@murrayrm

What happens if you have a LinearIOSystem, which is both an InputOutputSystem and a StateSpace system? Should the call signature be sys.dynamics(t, x, u) or sys.dynamics(x, u)? How would what you're proposing work?

I see 2 options:

  1. Make both compatible consistent with scipy's solve_ivp and put the t argument first.
  2. put the t argument at the end as an optional keyword argument and possibly raise a warning if it's not None for LTI systems (can LinearIOsystems be time-varying?).

In iosys, for numerical integration using solve_ivp, it creates a separate function ivp_dynamics, partly because solve_ivp doesn't have accomodation for a u. So there's no particular need to have dynamics keep the t argument first. So I think I favor option 2 given the relative importance of t vs. x and u arguments.

Also, what happens if a system has parameters (allowed for I/O systems)? Should I be able to set those parameter values when I call the (public) dynamics function? If not, how do I change parameter values?

NOt sure how params are used in IOSystems, but it looks like the old _rhs method doesn't accept them as currently written. Seems like it should be possible to revise it in the future to accomodate those parameters.
For LTI systems, we could also allow a params dict as a keyword argument for consistency's sake, but raise a warning if it's not empty for LTI systems? (which are immutable).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@murrayrm

murrayrm commented Feb 17, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

No matter what, I think we want the InputOutputSystem and StateSpace signatures for dynamics and output to be the same. Otherwise it will be very confusing.

If we leave in t, I think it should be listed first:

  • Consistent with SciPy but also with MATLAB (eg, ode45).
  • Consistent with output arguments (t, y = step_response(sys)) and input arguments (t, y = initial_response(sys, t, x0), t, y = forced_response(sys, t, u, x0).

Another possibility would be to define dynamics and output for LTI systems and then define these for InputOutputSystems with a signature of the form sys.dynamics(x, u[, t]) where t = 0 by default. This would then give the same signature for both and would make t optional (which is certainly going to be the most common case).

However, I would leave the signatures for _rhs and _out unchanged, so that when when you define a nonlinear system that the argument order would be t, x, u (consistent with MATLAB and SciPy [minus the u argument]). So for

sys = NonlinearIOSystem(updfcn, outfcn)

the signatures of updfcn and outfcn are still f(t, x, u). If we change this order, then we break existing user code and are incompatible with other packages. Of course, we could just redefine things internally, but from a developer point of view it is might be tedious to try to remember to flip things around all the time.

I'll need to look through the params processing. It should be the case that is optional but if specified then it overrides the local parameter dictionary stored in the instance. See examples/steering.ipynb to see examples of how it is used.

FYI: for the iosys module, LinearIOSystem already issues a warning if you pass parameters. We could do the same for state space.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

sawyerbfuller added 2 commits February 17, 2021 09:35
@sawyerbfuller
updated statesp.dynamics and output to put optoinal time argument t f…
b010b99
…irst; more coverage in unit tests
@sawyerbfuller
LinearIOSys methods dynamics and output set to be from StateSpace.dyn…
a2eafc0
…amics and output directly
@sawyerbfuller

sawyerbfuller commented Feb 17, 2021

Copy link
Copy Markdown
Contributor Author

OK good point, it looks like there's a pretty strong convention of leaving t first in the library and elsewhere so I'm in favor of sticking with that, I think it's not too bad.

The latest commit changes Statesp.dynamics and output to have t first, and to ignore it.

With this, the convention now is that the call signature is dynamics(t, x, u) and output(t, x, u) everywhere, including what used to be _rhs and _out. Perhaps remaining to be done, in a future commit, is to allow for a params argument.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ilayn

ilayn commented Feb 17, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

You can also use it in scipy.solve_ivp with something like scipy.solve_ivp(lambda t, x: P.dynamics(x, -K@x), Tfinal)

Yes instead of doing that you can actually call the dynamics or the output methods and obtain a function so it becomes

.....
    def dynamics(self):
        def f(x, u):
           return self.a.copy() @ x + self.b.copy() @ u
        return f
....

And then you can ask for that function and use it elsewhere

f = P.dynamics()
solve_ivp(f, t, x0, ....)

So even P is gone f will survive and become independent from the model itself hence the method becomes a constructor. But maybe this is not what you want.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@murrayrm

murrayrm commented Feb 17, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

It seems like in the current version, the 't' argument isn't really "optional". In particular, if you don't want to specify 't' I think you would have to call the function as sys.dynamics(x=x, u=u), which looks funny to me.

An alternative would be to define dynamics and output so that they take a variable number of arguments, with the idea that dynamics(x, u) would set t=0 and dynamics(x) might set t=0 and u=0. You could also add a param argument.

If you do that change, I would leave _rhs and _out in place for iosys since you don't want the argument processing to have to be done each time you call the function (which happens a lot in a simulation).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

roryyorke
roryyorke reviewed Feb 18, 2021
View reviewed changes
Comment thread control/statesp.py
dx/dt or x[t+dt] : ndarray of shape (self.nstates,)
"""

out = np.zeros((self.nstates, 1))

@roryyorke roryyorke Feb 18, 2021

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I might be missing something, but vectors can have ndim=1, and the matrix products and so on will work fine. Do you need ndim=2 for the vectors here?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@sawyerbfuller sawyerbfuller Mar 11, 2021

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

you're right. I thought there might be ambiguity in how dot works on 1D vectors vs column vectors on square arrays, but it looks like it does the right thing for 1D vectors: it interprets them as column vectors. so new PR will return 1D vectors.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@murrayrm murrayrm mentioned this pull request Feb 25, 2021
Release 0.8.4 and 0.9.0 sequencing #451
Closed
@sawyerbfuller
sawyerbfuller marked this pull request as draft March 9, 2021 17:06
@sawyerbfuller

sawyerbfuller commented Mar 11, 2021

Copy link
Copy Markdown
Contributor Author

Have decided I should start over and create a new PR.

In the meantime I thought I would do a quick benchmark to find out whether there are notable speed increases using a callable rather than the dynamics method, which has a few more attribute lookups. The results suggest that there's not much of a difference, so I think I'll just stick with having the method:

import control as ct
import time
sys = ct.ss([[-1.37394092,  0.08654488],
        [-0.47735689, -0.43755742]], [[-0.27547913],
        [ 0.        ]], [[-0.03284931, -0.        ]],  [[0.]] )

def dynamics(self, *args):
    if len(args) not in (2, 3): 
        raise ValueError("received"+len(args)+"args, expected 2 or 3")

    if np.size(args[1]) != self.nstates:
        raise ValueError("len(x) must be equal to number of states")
    if len(args) == 2: # received t and x, ignore t
        return self.A.dot(args[1]).reshape((-1,)) # return as row vector
    else: # received t, x, and u, ignore t
        #x = np.reshape(args[-2], (-1, 1)) # x must be column vector
        #u = np.reshape(args[-1], (-1, 1)) # u must be column vector
        if np.size(args[2]) != self.ninputs:
            raise ValueError("len(u) must be equal to number of inputs")
        return (self.A.dot(args[1]) + self.B.dot(args[2])).reshape((-1,)) # row
setattr(ct.StateSpace, 'dynamics', dynamics)

def dynamics_callable(sys): 
    return lambda t, x, u: sys.A.copy()@x + sys.B.copy()@u
dynamics = dynamics_callable(sys)

time1 = time.time()
x = sp.integrate.solve_ivp(sys.dynamics, (0,1000), (0,0),  args=((1,),))
print(time.time() - time1)
time2 = time.time()
x = sp.integrate.solve_ivp(dynamics, (0,1000), (0,0), args=((1,),))
print(time.time() - time2)

prints out:

0.1018521785736084
0.08774900436401367
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sawyerbfuller

sawyerbfuller commented Mar 11, 2021

Copy link
Copy Markdown
Contributor Author

@murrayrm

It seems like in the current version, the 't' argument isn't really "optional". In particular, if you don't want to specify 't' I think you would have to call the function as sys.dynamics(x=x, u=u), which looks funny to me.

makes sense.

An alternative would be to define dynamics and output so that they take a variable number of arguments, with the idea that dynamics(x, u) would set t=0 and dynamics(x) might set t=0 and u=0. You could also add a param argument.

One possible problem arises is if t is optional and two arguments are received: are they t and x or x and u?

To solve that ambiguity without keyword arguments, it seems like t has to be a required argument. And we just ignore it for LTI systems. Seem reasonable? not sure what else we could do there.

If you do that change, I would leave _rhs and _out in place for iosys since you don't want the argument processing to have to be done each time you call the function (which happens a lot in a simulation).

I can do that. Though it appears that arg processing doesn't actually seem to impact speed much, see above.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@murrayrm

murrayrm commented Mar 11, 2021

Copy link
Copy Markdown
Member

For _rhs versus and dynamics and arg processing: the optimal package calls _rhs thousands of times, so it might be worth checking the benchmarks in PR #549 and making sure they aren't affected.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sawyerbfuller sawyerbfuller mentioned this pull request Mar 11, 2021
new dynamics and output functions for statespace and iosystems #558
Merged
@sawyerbfuller

sawyerbfuller commented Mar 11, 2021

Copy link
Copy Markdown
Contributor Author

I’ll add a note in the doc strings about the different anticipated use cases for _rhs vs dynamics (Fast vs user friendly) In the new version of this pr, #558.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sawyerbfuller sawyerbfuller closed this Mar 11, 2021
@murrayrm murrayrm mentioned this pull request Mar 20, 2021
change u == 0 test to avoid py3.8+ syntax warning #580
Merged
@murrayrm murrayrm modified the milestone: 0.9.0 Mar 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

1 more reviewer
@roryyorke roryyorke roryyorke left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@sawyerbfuller @coveralls @ilayn @murrayrm @roryyorke
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.