Closed ChrisRackauckas closed 8 months ago
I would like to emphasize a requirement (that is somewhat hidden in the list above): If people do not use something like sensitivity analysis, they should not need to care about this and can still use everything they have used before. For example, we pass our semidiscretization as p
in Trixi.jl, containing numerical parameters, the mesh, caches for mutating operations etc.
Yes important point, added to the list.
These also seem related: SciML/DiffEqParamEstim.jl/issues/107 SciML/DiffEqParamEstim.jl/issues/102
- https://github.com/Metalenz/FlexibleFunctors.jl might be a better starting point than Functors.jl (to the point where starting as a fork may be helpful).
Note that FlexibleFunctors is now part of Functors.jl.
See also the monster discussion in https://github.com/invenia/ParameterHandling.jl/issues/43. TL;DR this is fiendishly complex to do in general, but it would be amazing to have more standardization around interfaces.
We don't have to be too general, just have an interface which provides everything SciML needs, and update it as SciML needs more.
Suggestion: Add https://github.com/paschermayr/ModelWrappers.jl to the guiding designs?
I talked with @YingboMa and the approach we're going to go with is to prefer canonicalization, i.e. the internals to the solvers won't need any fmap
or special broadcast because everything will be represented by simple arrays, and then this would mean that we're restructure/destructure at the boundary to the user (f
calls and getproperty
overloads) to mask this.
Does that have any potential (performance?) impacts on using parameter objects with mixed types (i.e. including a mix of functions, floats, ints, etc in a type-stable manner)? Or would this only be enforced in places where having a fixed type for parameters is needed anyways for the problem being solved to make sense?
It should be more efficient. We plan to have a non-allocating restructure!/destructure!
Edit: Thought this was under ModelingToolkit. Making a separate issue over there instead.
All,
We are redesigning the operator interface to SciML.ai in https://github.com/SciML/SciMLOperators.jl. This issue has come up in discussions on how to pass in parameters to function objects. The problem here, as I see it, is that there are two kinds of parameters we want to pass to functions/operators:
The former can in most cases be cast as an AbstractArray
, while the latter is almost always a complex data-structure. Currently SciML
only permits passing in one parameter via the p
positional argument in f(du, u, p, t)
. Problems arise when one has to do adjoint calculations with p
when it is a complex data-structure. Obviously writing restructure, restructure methods for all the data-types in p
can become cumbersome.
https://github.com/SciML/SciMLOperators.jl/pull/143 is solving this problem by allowing users pass the two types of parameters separately. The proposed design would allow you to pass array-like AD parameters in p
, and function-dependent data-structures using keyword arguments:
# Test scalar operator which expects keyword argument to update, modeled in the style of a DiffEq W-operator.
γ = ScalarOperator(0.0; update_func=(args...; dtgamma) -> dtgamma, accepted_kwarg_fields=(:dtgamma,))
dtgamma = rand()
@test γ(u,p,t; dtgamma) ≈ dtgamma * u
https://github.com/SciML/SciMLStructures.jl is the solution.
Guiding issues:
Guiding designs:
For a long time we were able to get away with just "let
p
be any struct", though an edge case came up, and then a second one, and then a third one, etc. until it became clear we need to at least have an interface defined for "what can be done withp
". Similarly, we have allowed a loose definition ofu0
as "anything that can broadcast effectively, and if required can do linear algebra", but this has become limiting.The main issue really started cropping up with adjoint sensitivity analysis. In that case, one might want to extend the differential equation to include
p
, e.g.[u0;p]
forInterpolatingAdjoint
, which then means thatp
needs to be able to "act" like au0
. If that's the case, then if we assumeu0
needs to be able to broadcast and do linear algebra, suddenly this requirement is also onp
. While this works for "most"p
's that people use in practice, it does impose a limitation where using say a tuple of arrays for parameters works for "standard" equation solving, but fails with adjoints.This then requires that we ask the question: what is "the" SciML parameters or structure interface? Currently it has been essentially
AbstractArray
, sinceAbstractArray
is the only thing that effectively defines linear algebra, with extensions allowed with extra functionality is not used. However, it's probably more than overdue to both tighten and enlarge this interface by directly defining what it's actions should be.If we do this right, then people should be able to use weird structures everywhere and have things "just work", or tell them which functions to define in order for things to just work. How do we get there?
What should a SciML Parameters interface do?
p
's still need to work in the solvers: e.g. only the places that need more control should checkisscimlstructure
(and if false, throw an error that this is required).Ideas from Other Libraries / Do we need a separate library for this? Can we use another one?
Questions
Do we extend the solvers to allow for a SciML Structure/Parameter for
u0
or always flatten?This is potentially a considerable amount of work, but with a potentially considerable amount of gain. It would be an interesting solution to the adjoint sensitivity problem: if
[u0,p]
"just works" because arrays of arrays just work because arrays of arrays are supported in the structure interface, then some code becomes easier. But other things, like how to define mass matrices, become 😕. Maybe all matrices need to only be defined on the flattened form, and then LinearSolve.jl can do the restructure/destructure to make it all work nicely?Supporting this in OrdinaryDiffEq.jl is probably not "too" hard. Every
2 * k1
would need to be like afmap(*,k1,2)
. It would probably be best to just define that as an operator2 ⊗ k1
or something so the code is easier to read, but that would up the barrier to entry. For the broadcast operations, it would just require that we specialize@..
effectively, which would probably be the hardest part (ifall(isSciMLStructure,vals)
-> special dispatches?).@..
would no longer just be "Fast Broadcast" anymore though, since it would have a different definition/operation on things like arrays of arrays (but then would allow their support without requiring a RecursiveArrayTools.VectorOfArray wrapper, which groups like DynamicalSystems.jl have been asking for years).For other solvers, we'd have to do destructure/restructure in the wrapper code so it just presents as arrays to the underlying solver. To not have a performance hit, we will need (6) to hold.
I think we should do it, but it will be a challenge.
Do we provide a tables interface for reading/writing to data?
I can see this as very valuable for people working with CSVs and the such. https://github.com/rafaqz/ModelParameters.jl showed it's possible.