narijauskas / PRONTO.jl

A Julia implementation of PRONTO
https://narijauskas.github.io/PRONTO.jl/dev
MIT License
20 stars 2 forks source link

`PRONTO.SymbolicModel` instead of `generate_model` #13

Open zsunberg opened 1 year ago

zsunberg commented 1 year ago

EDIT: See comment below - I think it is a much better idea.

Currently generate_model works by adding new methods for functions in the PRONTO module that dispatch on the current user-defined Model type. This is somewhat implicit (explicit is better) and modifies the global state of the program and the user doesn't have anything concrete to "hold on to" that represents all the new code that has been created.

One alternative would be to return an object from generate_model. (Of course there might be better names for all of this, and I have renamed the current PRONTO.Model to ModelParameters).

~~struct GeneratedModel{ID, P<:ModelParameters} params::P

other data and options could also go here

end~~

The ID parameter could be a UUID, or an Int, or there could be an option for the user to name it something they like, or it could be a Symbol starting with the name of the function and some random characters.

Then, the built functions could dispatch on this object, i.e. the generated method would have the signature PRONTO.lxx(m::GeneratedModel{UUID("dc5e5d6a-fcac-11ed-152d-8b59e1851479")}, x, u, t) or just PRONTO.lxx(m::GeneratedModel{1}, x, u, t) This gives the user an object that they can use in the solver, get information from, etc. It also might allow for more flexibility and options in the future. E.g. there could be an option to use symbolic differentiation or autodiff, and you could compare the results with two GeneratedModels created with the same ModelParameters and dynamics function.

Other options would be to include the built functions as members of GeneratedModel, but this would result in a lot of type parameters in GeneratedModel.

zsunberg commented 1 year ago

Or, perhaps, having both GeneratedModel and ModelParameters is a bit redundant.

Instead, you could just have a single PRONTO.SymbolicModel{ID} type that can be dispatched on. The constructor would do all the work currently done by generate_model, so, if the user just wants to give a dynamics function without any explicitly-defined parameters, usage would look something like

model = PRONTO.SymbolicModel((4, 1), dynamics, stagecost, termcost, regQ, regR)

Or, if the user wanted their own parameters type, they could still do

@kwdef struct Spin2 <: FieldVector{3, Float64}
    kl::Float64 # stage cost gain
    kr::Float64 # regulator r gain
    kq::Float64 # regulator q gain
end

model = PRONTO.SymbolicModel((4, 1), dynamics, stagecost, termcost, regQ, regR, params=Spin2(0.01, 1.0, 1.0))

SymbolicModel could either be immutable and users could create a new model if they wanted to change the params, or you could provide setparams!(model, params).

(I think this is a much better idea than what I described above)