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

RFC: Implement a "Problem" style synthesis interface #307

Open
imciner2 opened this issue May 27, 2020 · 6 comments
Open

RFC: Implement a "Problem" style synthesis interface #307

imciner2 opened this issue May 27, 2020 · 6 comments
Labels
hacktoberfest https://discourse.julialang.org/t/hacktoberfest-2020-x-the-julia-language/46927

Comments

@imciner2
Copy link
Contributor

I was thinking about how the synthesis functions are setup currently, and I think currently they are still very "Matlab-y," which I at least find somewhat awkward to work with because you have to pass around every matrix as an argument (this can grow to a very large number of arguments if you start doing things like linear predictive control, where I have written functions with 7 or more different arguments). On the other hand, I think the LQG type is somewhat too much, where it even does the controller design for you in the constructor instead of in a separate function like you have to do for the rest of the controller types.

I have found that I think I like the idea that the differential equations packages use, where they define problem types that contain the parameters for the problem, and then pass those into the solver functions. I think that would be a nice direction to move to, and possibly then also support a single design function that each type overloads to design the controller. That provides a nice consistent interface (for instance, it removes the need for me to remember whether the function I wanted was lqr, dlqr, lqrd, lqg, etc. and their argument orders since I define everything when I create the problem instance).

Specifically I am thinking of an initial type hierarchy of:

Abstract type ControlProblem
--> Concrete type LQR
--> Concrete type LQG

Then for instance the LQR type has fields for sys, Q, R, S, P, N, and then the synthesis functions have all the information they need inside this type to do the problem synthesis and return the controller matrix. The synthesis functions then take their sampling time from the included system (as is currently done by the overloaded lqr(sys, Q, R) function).

@baggepinnen @mfalt @olof3 Thoughts?

@baggepinnen
Copy link
Member

I do like the idea in general, I'm wondering though, how many types would we need and how much would we save from the change?
If the type LQR needs sys, Q, R, S, P, N, what is the real difference of just calling the function lqr(sys, Q, R, S, P, N) as opposed to design(LQR(sys, Q, R, S, P, N)). I see the benefit in cases where the design has to be carried out a large number of times, but for the traditional user designing a single controller for a single system, I can't immediately see any benefit.

Somewhere you still need to make all the decisions and supply all the parameters, so you might shift the problem from remembering the function name to remembering a type name or a keyword name?

In general though, we have realized this issue before (hence the current LQG type) and I would sure like to see some steps in this direction :)

@imciner2
Copy link
Contributor Author

imciner2 commented May 27, 2020

Yes, while it may not save much for simple design patterns, there are cases where I think it will. For instance, if you have a continuous-time system and a continuous-time LQR problem for it but want a discrete-time controller. I am envisioning that you could then do

cProblem = LQR( contsys, contQ, contR, contS, etc. )
dProblem = c2d( cProblem )   # this could even be made an inplace operation, e.g. c2d!
K = design( dProblem )

This would transform the problem from the continuous one to the discrete one, doing the c2d for both the provided system and the cost matrices to ensure the problem is actually equivalent (see the introduction to [1] for how this would be done). Without the problem types, the c2d would need to return a tuple of all the matrices. (and yes, this actually is the equivalent to lqrd in Matlab, but again, the naming of the functions isn't very helpful in my opinion).

Another place this could help is actually the work I do for predictive control. In there we do analysis using the problem parameters when analyzing the implementations, so the system and cost matrices need to be passed around to various functions for performing different types of analysis and design.

We don't necessarily have to replace the current functions - they could be left for compatibility and to help people switching from Matlab-esque languages - but I think the pattern of defining control problems can allow for cleaner expansion of the toolbox and a nicer interface.

[1] E. Bini and G. M. Buttazzo, ‘The Optimal Sampling Pattern for Linear Control Systems’, IEEE Transactions on Automatic Control, vol. 59, no. 1, pp. 78–90, 2014, doi: 10.1109/TAC.2013.2279913

@olof3
Copy link
Contributor

olof3 commented May 27, 2020

I agree that the current LQG might be doing too much to be useful.
I agree that it is a bit annoying with all the different arguments, but like @baggepinnen said, I think it would be hard to get rid of much of the problems.

Are you thinking about LQG or more general synthesis problems? For LQG don't think the situation is that bad. But I definitely see that it could be convenient to include a disturbance/noise model in the plant specification. It could also be nice to bundle signal limitations into a problem. However it seems difficult to provide something that is sufficiently general to be useful.

On a side note there are many issues that lead to confusion with the lqg functionality. The naming of the functions is horrible, to say the least. At least the one of lqrd and dlqr that handles sampled-data systems should be renamed to something like lqr_sd. Also it would be a bit more symmetric with lqrc and lqrd. Also the cost and covariance matrices should perhaps be named something like Qx or Qxx, Qxu, Quu, Rww, Rvv or Rnn

So what you want to do seems to be
lqr(sys, Qxx, Quu, Qxu)
and then something like lqr_sd(sys, Ts, Qxx, Quu, Qxu) (always tricky with the order of variables), or perhaps lqr_sd should just take a discrete-time system and have the same arguments as lqr.
This functionality does not seems complicated enough to warrant the introduction of a new type.

@imciner2
Copy link
Contributor Author

At least the one of lqrd and dlqr that handles sampled-data systems should be renamed to something like lqr_sd.

Careful, dlqr and lqrd have different behavior. dlqr assumes the system and matrices are already in discrete time when it does its discrete-time design, whereas lqrd takes a continuous-time problem and does a discrete-time design after essentially doing a c2d on it (this also converts the matrices into discrete-time).

However it seems difficult to provide something that is sufficiently general to be useful

Yes, trying to put everything inside a type is not feasible - and I don't advocate for making everything inside the type, but I think there is a large subset of the problem data that is relevant (for LQR, the parts I mentioned in the 1st comment seem to be the ones that are most revelevant). I am not too familiar with LQG design to be honest, but when I was drafting this proposal I saw the current implementation and felt it went a bit too far down the typing route. I am envisioning these problem types as a data container that allows for better portability of the parameters. For instance, here is a sample of generating an MPC problem in a toolbox I am working on (I am working on porting it to Julia right now):

# Create the cost function matrices
H = condensed_hessian_gen(A, B, Q, P, R, N);
G = condensed_linear_gen(A, B, Q, P, N);
L = compute_precond( A, B, Q, R, N )

As you can see, these functions require basically the same subset of problem data to operate on, which is what led me to want to create a type to hold the data.

While I could do this in my package, I was wanting to propose it for "upstream" so that there isn't a lot of fragmentation in the styles of packages in the developing control ecosystem. This can also be useful for providing benchmark/example problems in a nice concise way. Instead of giving the user a bunch of matrices in a specific order that they have to keep track of, they get the problem struct containing the data.

@baggepinnen
Copy link
Member

If you have such a type holding the relevant problem data (A, B, Q, P, R, N in this case), feel free to submit a PR with the type and how you'd imagine it'd be used, possibly together with a translation from the the old style syntax to the new, .e.g.,

lqr(...) = design(LQR(...))

I find it easier to reason about a proposal when there is some code to look at, and the look-and-feel also also becomes a bit more clear :)

@olof3
Copy link
Contributor

olof3 commented May 27, 2020

dlqr and lqrd have different behavior.

Yes, it is impossible to remember which one is which, so the naming is awful. The one that solves the sampled-data problem (discrete controller for continuous-time plant and cost function) should have a name that clearly indicates that it is doing so.

@baggepinnen baggepinnen added the hacktoberfest https://discourse.julialang.org/t/hacktoberfest-2020-x-the-julia-language/46927 label Oct 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hacktoberfest https://discourse.julialang.org/t/hacktoberfest-2020-x-the-julia-language/46927
Projects
None yet
Development

No branches or pull requests

3 participants