Skip to content
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

more abstract statespaces #521

Merged
merged 8 commits into from
May 28, 2021
Merged

more abstract statespaces #521

merged 8 commits into from
May 28, 2021

Conversation

baggepinnen
Copy link
Member

No description provided.

@JuliaControlBot
Copy link

Something failed when generating plots. See the log at https://github.com/JuliaControl/ControlExamplePlots.jl/runs/875149431?check_suite_focus=true for more details.

@baggepinnen
Copy link
Member Author

I'm a bit puzzled by
https://github.com/JuliaControl/ControlSystems.jl/pull/521/checks?check_run_id=2671828259#step:6:272
@olof3 it seems you touched this line last, what is this supposed to test? It is actually supposed to not work? In that case, @test_broken is the wrong thing to use.

@olof3
Copy link
Contributor

olof3 commented May 26, 2021

I'm a bit puzzled by https://github.com/JuliaControl/ControlSystems.jl/pull/521/checks?check_run_id=2671828259#step:6:272
@olof3 it seems you touched this line last, what is this supposed to test?

It's supposed to test feedback between a system and a number, when there are direct terms in the system.

I guess it was failing previously, although we want it to pass. That is the use case for @test_broken, right?

The improved, more involved, feedback method that I have added, although not very much integrated, handles this case. The older, simpler feedback method does not handle direct terms in both systems. So it seems like your changes has changed how the dispatch is done.

Good that it works, but perhaps we would like a simpler, more specialized, and faster feedback method for this standard use case.

I have been looking into this, but I think we need to discuss how much it makes sense to specialize.

@baggepinnen
Copy link
Member Author

I guess it was failing previously, although we want it to pass. That is the use case for @test_broken, right?

Correct, but the line below indicates that positiv feedback should provide the answer that we tested as broken on this line, i.e., both these can't pass?

@test_broken feedback(G3, 1) == ss(-1.5, 0.5, 0.5, 0.5) # Old feedback method
@test feedback(G3, 1, pos_feedback=false) == ss(-1.5, 0.5, 0.5, 0.5)

@baggepinnen
Copy link
Member Author

Ah sorry, yes they should both pass. I'll change the test to not broken anymore :)

@baggepinnen
Copy link
Member Author

In fact, the change made all calls to feedback dispatch to the general method as opposed to the old feedback(::StateSpace, ::StateSpace). All tests seem to pass except this one, so I guess the general method you implemented @olof3 is working well. In that case, we can just remove the simple method and let the advanced method do all the work?

@baggepinnen
Copy link
Member Author

baggepinnen commented May 26, 2021

Good that it works, but perhaps we would like a simpler, more specialized, and faster feedback method for this standard use case.
I have been looking into this, but I think we need to discuss how much it makes sense to specialize.

The simple feedback specialized on StateSpace. If one actually wants performance, using StaticArrays is going to be required and the simple feedback does not help (unless adopted for HeteroStateSpace with static arrays). I think it's fine to only have the special method. Some timings, where simple_feedback is the old implementation and the systems have 10 poles and 1 y/u

@btime simple_feedback($s1,$s2)
# 3.327 μs (17 allocations: 10.16 KiB)  with normal arrays
# 262.156 ns (0 allocations: 0 bytes) modified to work with static arrays

@btime feedback($s1,$s2) # 6.752 μs (60 allocations: 14.39 KiB) New, more general feedback

@codecov
Copy link

codecov bot commented May 26, 2021

Codecov Report

❗ No coverage uploaded for pull request base (dev@490ba4d). Click here to learn what that means.
The diff coverage is n/a.

Impacted file tree graph

@@          Coverage Diff           @@
##             dev     #521   +/-   ##
======================================
  Coverage       ?   84.87%           
======================================
  Files          ?       31           
  Lines          ?     3187           
  Branches       ?        0           
======================================
  Hits           ?     2705           
  Misses         ?      482           
  Partials       ?        0           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 490ba4d...2fc8340. Read the comment docs.

@olof3
Copy link
Contributor

olof3 commented May 26, 2021

If one actually wants performance, using StaticArrays is going to be required and the simple feedback does not help (unless adopted for HeteroStateSpace with static arrays). I think it's fine to only have the special method. Some timings, where simple_feedback is the old implementation and the systems have 10 poles and 1 y/u

I am a little bit worried that the general feedback method is a little bit too involved . Also, 50% faster for free (less for larger system, more for smaller ones), without having to worry about static arrays seems worthwhile (although I think the performance of the general version could be improved, at the cost of readability) .

The alternative would be to update the simple feedback version to handle direct terms (and probably also positive feedback), although the additional code required is a bit off-putting. I am not sure if there is a simple way to get the dispatch to work, but one could consider renaming the general version to feedback_general or something.

All in all, I don't have that much of a preference. What do you think @mfalt ?

@mfalt
Copy link
Member

mfalt commented May 26, 2021

If we could dispatch to a faster method for SS that would be nice, otherwise there is little reason to keep it. If the faster method only works for some cases I guess we could settle for something like this: (psuedo code:)

function feedback(s1::SS, S2::SS)
 if one_is_zero(s1.D,s2.D)
   feedback_simple(s1,s2)
else
 feedback_general(s1,s2)
end
end

Edit: this might have some potential downsides, but since we know all the types it would be type-stable. It might be worth it.

@olof3
Copy link
Contributor

olof3 commented May 26, 2021

If we could dispatch to a faster method for SS

with SS you also mean HeteroStateSpace? There should be no difference in the implementation. I think it was just the old type signature that was limiting.

one_is_zero(s1.D,s2.D)

This would not be the main difference. The general version checks and exploits this.

The drawback with the generic version is that it allocates a bit more and does unnecessary multiplications with matrices with 0-length dimensions.

@baggepinnen
Copy link
Member Author

I'm usually for more performant implementations, but in this case it does not feel worth it to me. The call to feedback is probably not going to be the bottleneck in any performance sensitive code, and if you are concerned with the 2x performance hit you can actually get a 10x improvement by not using the regular state-space with heap-allocated arrays.

@olof3
Copy link
Contributor

olof3 commented May 26, 2021

Yes, only performance wise I agree it's not worth it.
Seems like a little bit of an overwhelming method for a first time user though, and in the docs it seems like it could be difficult to get the main idea across for the basic use case while still capturing the advanced capabilities. Always nice to look at the code one is using and that it is easy to understand.

Perhaps a nonstandard doc string would be the solution. With simple use case first, and then separately, below, the current one.

@baggepinnen
Copy link
Member Author

Note that this pr doesn't add nor remove any docstrings, it simply removes one method which was strictly less capable that another existing method. I'm not even sure that maintaining separate implementations for educational purposes is going to be easier for new users, having to figure out which of several implementations of the same function will be called and why is likely much more confusing than just having one generic method.

Docstring can certainly be improved though. The docstring for feedback is probably the one from this package I read the most often and scratch my head about.

@baggepinnen
Copy link
Member Author

I added a block diagram to the docstring in 1658340
see if it makes sense and if it's actually a correct depiction of the signals, I wasn't completely sure :P

@olof3
Copy link
Contributor

olof3 commented May 27, 2021

I made a small further stab at the docstring and added a diagram for the basic use case as well. Feel free toimprove them further. I guess these methods (and docstrings) should really be for any LTISystem?

@baggepinnen
Copy link
Member Author

LGTM

I guess these methods (and docstrings) should really be for any LTISystem?

The general version currently only works for ss, but the block diagram for the simple usage applies to transfer functions as well. This usecase already has a small block diagram associated with it in the other docstring
https://github.com/JuliaControl/ControlSystems.jl/pull/521/files#diff-40aa26d41b0c99b2f88018c7c8e914ee958ce3cb5e7fb1c11902eef04f06627fR206
so users looking at ?feedback will see both.

@olof3
Copy link
Contributor

olof3 commented May 27, 2021

The general version currently only works for ss, but the block diagram for the simple usage applies to transfer functions as well. This usecase already has a small block diagram associated with it in the other docstring

Hopefully I'll get around to fix the general version to support DelayLTISystem. Possibly also for transfer functions.

so users looking at ?feedback will see both.

That is not ideal. Not sure what the best way is for getting maintainable non-duplicated docs? Does this happen if the methods for the different types are grouped together?

@baggepinnen
Copy link
Member Author

With the latest commit, ?feedback shows the following

help?> feedback
search: feedback feedback2dof

  feedback(L)
  feedback(P1,P2)

  Returns L/(1+L) or P1/(1+P1*P2)

  ─────────────────────────────────────────────────────────────────────────────────────────────────────────

  feedback(sys)
  feedback(sys1,sys2)

  Forms the negative feedback interconnection

  >-+ sys1 +-->
    |      |
   (-)sys2 +

  If no second system is given, negative identity feedback is assumed

  ─────────────────────────────────────────────────────────────────────────────────────────────────────────

  feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace)
  feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;
           U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],
           Wperm=:, Zperm=:, pos_feedback::Bool=false)

  Basic use feedback(sys1, sys2) forms the feedback interconnection

             ┌──────────────┐
  ◄──────────┤     sys1     │◄──── Σ ◄──────
      │      │              │      │
      │      └──────────────┘      -1|
      │      ┌──────────────┐      │
      └─────►│     sys2     ├──────┘
             │              │
             └──────────────┘

  Advanced use feedback also supports more flexible use according to the figure below

                ┌──────────────┐
        z1◄─────┤     sys1     │◄──────w1
   ┌─── y1◄─────┤              │◄──────u1 ◄─┐
   │            └──────────────┘            │
   │                                        α
   │            ┌──────────────┐            │
   └──► u2─────►│     sys2     ├───────►y2──┘
        w2─────►│              ├───────►z2
                └──────────────┘

  U1, W1 specifies the indices of the input signals of sys1 corresponding to u1 and w1 Y1, Z1 specifies the
  indices of the output signals of sys1 corresponding to y1 and z1 U2, W2, Y2, Z2 specifies the
  corresponding signals of sys2

  Specify Wperm and Zperm to reorder the inputs (corresponding to [w1; w2]) and outputs (corresponding to
  [z1; z2]) in the resulting statespace model.

  Negative feedback (α = -1) is the default. Specify pos_feedback=true for positive feedback (α = 1).

  See Zhou, Doyle, Glover (1996) for similar (somewhat less symmetric) formulas.
  ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

  ─────────────────────────────────────────────────────────────────────────────────────────────────────────

  feedback(sys)
  feedback(sys1, sys2)

  Forms the negative feedback interconnection

  >-+ sys1 +-->
    |      |
   (-)sys2 +

  If no second system is given, negative identity feedback is assumed

There is clearly some redundancy there.

Splitting the docstring like this

  feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace)
  feedback(sys1::AbstractStateSpace, sys2::AbstractStateSpace;
           U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[],
           Wperm=:, Zperm=:, pos_feedback::Bool=false)

can actually be confusing since the second method would overwrite the first, why is the first row there at all? All keyword arguments have default values so that the first method exists follows from the second signature.

@olof3
Copy link
Contributor

olof3 commented May 27, 2021

There is clearly some redundancy there.

Yes, it would definitely be good to improve this. I am not sure how to do it in a good way though. I think you know better what works well. Perhaps a method feedback(::LTISystem, ::LTISystem) and putting the docs for the simple case there, removing all the redundant ones? Only keeping the docs for the complicated one for AbstractStateSpace?

why is the first row there at all? All keyword arguments have default values so that the first method exists follows from the second signature.

I agree, that was perhaps not the clearest way of doing it. But I think the basic use be illustrated in the docs somewhere, although perhaps just mentioning, as it is done in the doc text, would be sufficient. Feel free to improve it.

@baggepinnen baggepinnen merged commit c44544a into dev May 28, 2021
@baggepinnen baggepinnen deleted the moreass branch May 28, 2021 04:45
baggepinnen added a commit that referenced this pull request Nov 7, 2021
* Avoid unnecessarily large realization for feedback of TransferFunction (#485)

* Avoid unnecessarily large realization for feedback of TransferFunction

* Fix and added more tests.

* Change to numpoly, denpoly

* add dev brach to PR CI

* Switch u layout (#480)

* switch u layout for lsim

* Update src/timeresp.jl

Co-authored-by: Fredrik Bagge Carlson <baggepinnen@gmail.com>

* More updates, one error in test_timeresp

* Fix tests

* Change to AbstractVecOrMat

* Catch CuArray in matrix conversion

* General zero vectors for x0 to support GPUs

* Update src/timeresp.jl

Co-authored-by: Mattias Fält <mfalt@users.noreply.github.com>

* Update src/timeresp.jl

Co-authored-by: Mattias Fält <mfalt@users.noreply.github.com>

* Move f outside lsim

* Remove GPU compatible x0, save for later

* Fix doctest

* add kwargs

* Remove variable and generalize type

Co-authored-by: Fredrik Bagge Carlson <baggepinnen@gmail.com>
Co-authored-by: Mattias Fält <mfalt@users.noreply.github.com>

* Gangof4 fixes (#486)

* QOL improvements for plotting

* remove spurious getindex

* update docstring

* multiple Ms in nyquist

* bugfixes

* make rings appear in all subplots

* Minor fixes to gang-of-four functionality.

* Fixes to Nyquist plots.

* Updated the nyquistplot docstring

Co-authored-by: Fredrik Bagge Carlson <baggepinnen@gmail.com>

* add frequency in Hz to dampreport (#488)

* updates to nyquistplot (#493)

* updates to nyquistplot

* Update src/plotting.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Update src/plotting.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* fix traces in rlocus (#491)

* fix traces in rlocus

* Update src/pid_design.jl

Co-authored-by: Albin Heimerson <albin.heimerson@control.lth.se>

Co-authored-by: Albin Heimerson <albin.heimerson@control.lth.se>

* let lsim handle arguments in lsimplot (#492)

* bugfix: avoid creating continuous system with Ts=0.0 (#496)

* Deactivate _preprocess_for_freqresp (#497)

until hessenberg is properly used

* allow balance when converting tf to ss (#495)

* allow balance when converting tf to ss

* use zeros(T) instead of fill(zero(T))

* Update src/types/StateSpace.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Update src/types/conversion.jl

Co-authored-by: Albin Heimerson <albin.heimerson@control.lth.se>

Co-authored-by: olof3 <olof3@users.noreply.github.com>
Co-authored-by: Albin Heimerson <albin.heimerson@control.lth.se>

* Better handling of problematic cases for delay systems (#482)

* delay error should be correct now

* warn limit

* Add tustin c2d/d2c method (#487)

* add Tustin discretization

* no indexing after c2d

* implement f_prewarp in tustin and test vs. matlab

* fixe use wrong Ts

* f_prewarp -> w_prewarp

* w_prewarp in tests

* correct handling of x0 in lsimplot (#498)

* move eye def to framework.jl (#499)

* Fix UndefVarError: T not defined (#501)

* add axes for ss (#504)

* pi place and tests (#502)

* pi place and tests

* Fix test

* Add ending space in file

* Update numvec denvec

* Fixed error

* Update test/test_pid_design.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Update test/test_pid_design.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Update forms

* Fix test

* Fix test

* Update src/pid_design.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Update src/pid_design.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Update src/pid_design.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Conver to tf in placepi (#507)

* Use nstates instead of nx in lsimplot (#508)

* Use nstates instead of nx in lsimplot

* Add predictor (#511)

* up innovation_form and add noise_model

* keep innovation_form, add predictor

* export predictor

* add hats

* updates to `obsv` (#517)

* updates to `obsv`

All computing an arbitrary number of rows in the observability matrix and accept `AbstractStateSpace`

* Update src/matrix_comps.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* `hz` keyword in `nyquistplot` similar to in `bodeplot` (#518)

* Fixes to place (#500)

* Add tests for place.

* Removed luenberger and exented place instead.

Co-authored-by: Fredrik Bagge Carlson <baggepinnen@gmail.com>

* allow AbstractStateSpace in several places (#520)

* allow AbstractStateSpace in several places

* Maintain type for zpk in c2d (#522)

* maintain zpk type in c2d

* Fix type in typeconversion for c2d tf

* Fix type in c2d tf/zpk

* Remove assertion on tf/zpk SISO for c2d

* Test for c2d(zpk(..)..) unbroken

* Keep MIMO assertion c2d tf

* Add comment and fix test

* Fix test

* h to Ts

* Fix test

* remove assert

* split into methods

* Remove asserts (#523)

* Replace asserts

* add error types

* fix conversion for custom types (#514)

* fix conversion for custom types

* special numeric_type for AbstractSS

* fix conversion from ss to tf without type

* more abstract statespaces (#521)

* omre abstract statespaces

* even more ASS

* remove simple feedback in favor of mor egeneral version

* propagate timeevol

* add block diagram to feedback docstring

* Updates to docstring

* fix docstring formatting

* delete redundancy in feedback docstring

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* add controller function (#512)

* add controller function

* rename controller and predictor

* Move plot globals to runtests (#531)

* Move plot globals to runtests

* Move plot globals to framework.jl

* return similarity transform from `balreal` (#530)

* display error when covar fails

* return similarity transform from balreal

* Update src/matrix_comps.jl

Co-authored-by: olof3 <olof3@users.noreply.github.com>

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* remove incorrect warning in pzmap (#535)

* support hz keyword in sigmaplot similar to bodeplot (#537)

* change formatting in dampreport (#536)

* change formatting in dampreport

* update dampreport to use ±

* even better formatting

* up formatting for complex systems

* add additive identity element for statespace and TF (#538)

* add additive identity element for statespace and TF

* rm zero from type. Test MIMO zero

* Fix struct_ctrb_obsv (#540)

* Fix struct_ctrb_obsv, closes #409

* Update src/simplification.jl

Co-authored-by: Fredrik Bagge Carlson <baggepinnen@gmail.com>

* add to docstring

Co-authored-by: olof3 <olof3@users.noreply.github.com>

* Yet another fix for  struct_ctrb_states. Closes #475 (#541)

* bugfix for negative real pole in damp (#539)

* Fix #546

* minor typographic changes

* optional epsilon in dcgain (#548)

* optional epsilon in dcgain

* Update analysis.jl

* bugfix: use correct type for saving dde solutions (#549)

Would be good to have a regression test for `BigFloat` data which was the reason I found this bug. That seems slightly involved to fix though.

I assume that this would also have failed for `ComplexF64`, so eventually a test for that too would be good.

* bugfix dcgain (#551)

* correct type of initial state in step (#553)

* remove version checks

* Fix spacing in type printing

* write `struct_ctrb_states` in terms of `iszero` instead of `== 0` (#557)

* write `struct_ctrb_states` in terms of `iszero` instead of `== 0`

The reason is that `iszero` always returns a bool, whereas `== 0` may return anything. The difference appears for symbolic variables where
```julia
julia> q0 == 0
q0(t) == 0

julia> iszero(q0)
false
```

* Update simplification.jl

* remove `issmooth` (#561)

* remove issmooth

* drop extra `]`

* Return a result structure from lsim (#529)

* Return a result structure from lsim

This is by design a non-breaking change to the lsim interface since the structure allows both getindex and destructuring to behave just like if lsim returned a tuple of arrays like before.  Indeed, the tests for lsim were not touched in this commit.

This commit also adds a plot recipe to the result structure. All plot recipes for lsimplot, stepplot, impulseplot have been replaced by the new recipe. This is a breaking change since the names of the previous plots no longer exist. A slight change is that the plots for a step response no longer show the text "step response", but I think that's an acceptable change, the user can supply any title they prefer themselves.

* remove automatic title

* introduce additional abstract result type

* do not destructure sys

* remove LQG (#564)

The functionality was very buggy and poorly tested. A much improved version with proper tests are available in https://github.com/JuliaControl/RobustAndOptimalControl.jl/blob/master/src/lqg.jl

* pole->poles tzero->tzeros (#562)

* update usage of plot for step simulation

* add doctest filters

* add release notes

* Update README.md

* Update README.md

Co-authored-by: olof3 <olof3@users.noreply.github.com>
Co-authored-by: Albin Heimerson <albin.heimerson@control.lth.se>
Co-authored-by: Mattias Fält <mfalt@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants