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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal to standardize squeezing output from systems #453
Comments
By the way, as far as I can tell this last came up ... almost ten years ago. https://sourceforge.net/p/python-control/mailman/message/27671663/ Looks like somehow the conclusion was to do something like the above options, but it was not explicitly spelled out. Somehow the frequency response functions ended up doing something different than the time response functions. Just realized, one of the participants, who I didn’t know at the time, is now a colleague of mine. Hi @eigensteve! |
This has been always an annoying thing. I would actually expect the squeeze to be the default behavior so it has to be turned off with |
Hi @ilayn, yes, the proposal includes that Just to confirm, would that be your preference? Can you see any reason to you would rather, when |
As fas as I experienced, you only need keepdims or squeeze True if you are doing a batch loop and you don't want to care about if the system in that spin of the loop is SISO or MIMO. For the rest, I can't think of any other usage. |
ok cool thanks. I'll tally that as a vote for #2 (please correct me if I'm wrong) |
I think this is something we should address in 0.9.0, since it is somewhat related to return of array vs matrix objects. Tagging it with that milestone. |
I'm taking a cut at this. PR coming soon, but some comments on things I found:
|
I see, i didnt realize that you have to select an input in the forced response functions, so you always get a (p, k) sized output for a MIMO system unless I think i was under the mistaken impression that the output would have
One minor difference is that in freq response functions, squeezing is achieved by selectig the zeroth element iff SISO whereas in time response it is currently accomplished by squeeze and no SISO test. I think best to use freq response behavior in time response functions, ie test for SISO and only squeeze if SISO and |
If we require that the system be SISO, then you could have the following scenarios:
These are counter-intuitive to me: it seems like if you call the function with |
I agree with @murrayrm. If you set squeeze=True (the default) and expect only one series you should get it as (k,) If you batch jobs with multiple systems, the most natural way is to explicitly set |
Let's see ..
So the corner case is code with interspersed SISO systems, and MIMO systems that sometimes have one output and using teh defaults. Output as currently coded will have different num dims depending on num outputs. Thsi is a pretty insignificant use case. I believe that SISO and MIMO are sort of like two different classes of system and that mimo systems should always produce mimo-like outputs. But if we are keeping the squeeze term then that suggests the numpy squeeze function and Im ok with functionality as it is currently in #507 in time resp functions. But then the freq response functions should have the same behavior and not test for SISO before squeezing along first two dimensions. |
Good point on frequency response. To make sure I understand it: if you compute the frequency response for a 2 input, 1 output system, right now you get a 3D array (roughly Y[output][input][freq]), independent of whether Similarly, if you have a 1 input, 2 output system and you squeeze, you get Y[output][freq] as the response. Right? |
I see a difference in time series vs frequency responses: A time series should always be at least |
@murrayrm Yes that sounds right. Not sure exactly how the code would look to squeeze along the first two dimensions in the freqresp functions thoigh. |
@bnavigator right now the docs and behavior return an array even for a single freuqency. |
i am a little worried that the addition of squeeze as a defaukt may break code that assumes a SISO system returns a 1D output when the default value is false. (might be hard to catch all instances of I think the "right" way to do this might be to be object-oriebted: to eliminate the squeeze keyword altogether and add an attribute to systems that allow a siso system to behave like a MIMO system (that is, sys.issiso() returns false even if it actually siso). don't know how soon I could get to that though, but i could put it on my list if it siunds interesting |
I think the following are all agreeable:
Assuming those are agreeable, a couple of possibilities:
Preferences? Other options? |
FYI, I have moved all of the processing of time and frequency responses into separate functions ( |
Nice synopsis. I agree on the four main points. and in particular agree that a frequency response of a siso system to a scalar should probably be a scalar. I think 5 is interesting but too complicated. 1 is probably good combo of simplicity & least change. My favorite is still 6 because it feels most object oriented, is opinionated, and would require the shortest docstring. But advocates of a feature have a responsibility to implement it, and my doc says it will be a few more weeks till I can type with both hands again (broken bone). And as @murrayrm suggested, the devil may be in the details. Changes I foresee include an update to the ‘conventions’ section to describe the ‘as_mimo’ feature and adding links to conventions on the time and freq response functions. My suggestion: do 1 and if a few weeks have gone by and 0.9 still isn’t released then maybe a PR will appear & you can take a look. But this isn’t important enough to hold up a a release waiting on my PR. |
1 sounds good to me. Except that I would prefer to keep it 1-d for every function when there is a vector involved in the output. In time series or in frequency responses. So |
I feel like #2 or #3 is most useful, since that lets you squeeze out dimensions that you don't need when you only have a "single" response. However, in all cases except when you happen to evaluate at a single time or frequency (using a list), then I think it is equivalent to #1. A related question is what the default should be. If we go with #1 and we leave squeeze=True as the default, then asking for a frequency response on a singleton list (which could come up) will not generate a list of frequency responses, but just a scalar. So you would have to use A middle ground (but perhaps overkill) would be to let
|
I like 2 better than 3 in that it keeps the output looking like the input, wheter it is a unit length array vs scalar. (I dont think its necessary to return a list if the user suppled frequencies as a list. am ok with returning all non-scalar outputs as numpy arrays.) i dont folow @bnavigator 's comment: should freqresp always return arays regardless of whether given omega is a scalar, or should it return a scalar for a scalar input? both freqresp and call are now vectorized. ultimately, I think @murrayrm has most experience with the lobrary and as cheif architect he should get final say. |
OK, here's the current plan:
This has the advantage that it captures all current behaviors in both time and frequency response, so if someone was relying on one of those then they can recover it by specifying |
Hi, this sounds good to me. S
|
I started to implement the changes required to implement the scheme above, but it was pretty messy and it lead to some counter-intuitive behavior (to me):
Because of this, I decided to pull out the time response portion of PR #507 into a separate PR (#511), focused on tightening the code and fixing an inconsistency with A couple of paths forward:
One thought is that we might think about having the following three behaviors:
The main change in the code is that instead of having For time responses, we could do the same thing. In particular, if you set |
PR's are now in place: #507 for frequency response, #511 for time response. The behavior described above is implemented, with one modification: the result from
I think this addresses the original problem that @sawyerbfuller raised. In particular, both time and frequency responses have the following behavior:
I have set up separate issue (#512) to track the creation of arrays of step and impulse responses. When that is implemented, time and frequency responses should have completely consistent behavior. |
Richard’s proposal seems good to me.
In #507, docstrings don’t yet indicate behavior of squeeze =none but I
assume that is to come assuming we go forward with this.
|
A further thought: am I right that the only difference now in Richard’s
proposal is that squeeze=true squeezes all axes, changing current behavior?
Just want to make sure that is an important enough use case to bother with
changing anything. I think we are all in agreement that the output should
match input (scalar-> scalar for siso, array-like -> array for siso), and
squeeze=true as proposed would change that, which nobody is asking for.
Feel free to let me know if I’m missing something. Is a bit complicated.
|
For frequency responses, you are basically correct. The "main" cases (SISO or full MIMO) are essentially the same. The main differences are what happens when you have a SIMO systems, a MISO system. The current behavior for a frequency response is that In the current PR #507 , you get the same default behavior (using For time responses, things are currently different, but PR #511 will make things consistent with whatever we decide (it is currently set up to match PR #507). What I think I like about the proposed system is that you can basically get three basic behaviors that are (relatively) easy to articulate and understand:
|
Ok, sounds good, sorry for causing extra work.
|
Proposal: When computing frequency and time response output of systems, standardize on indexing first element so output is a 1D array if system is SISO and
squeeze=True
. This is instead of applyingnumpy.squeeze
to output.Currently, there are two ways that python-control squeezes output:
apply 'squeeze' to the output at the end. This is what functions in
timeresp.py
do, e.g.forced_response
(edit: andiosys
time responses). An m x n MIMO system (m-output, n-input, m,n>1) produces a [m x n x k] array, where k is the number of data points in time. But a 1 x n system's output gets squeezed to [n x k] and a m x 1 is squeezed to [m x k].use indexing to extract the first element if SISO, otherwise leave a full array. This is what the frequency response functions
evalfr
,freqresp
do. If SISO, give [k] length array; MIMO systems always produce [m x n x k] arrays.I propose to standardize on the latter: a MIMO system should always output [m x n x k] arrays. Rationale: this facilitates interconnection and keeps indexing the system and its outputs consistent. Along with the proposal: retain the
squeeze=True
keyword argument because it's already in use, but do the 'squeeze' by indexing the first element, and only do it if SISO.Documentation would read something like:
"If squeeze is True (default) and sys is single input single output (SISO), returns a 1D array equal to the length k of the input, otherwise returns an (n_outputs, n_inputs, k) array for multiple input multiple output (MIMO) systems."
This is relevant for #449 and popped up in #442.
The text was updated successfully, but these errors were encountered: