Automatically set default state sensitivities for initial values that depend on parameters [edited]
- In #898, #899, #918 initial values are changed from floats to myokit.Expression objects, which are allowed to reference model variables.
- As a simplification, states are turned into floats when simulations are created.
- In this ticket, we ~should investigate keeping an "initial state" with expressions.~ will auto-set some of the state sensitivities so that dependencies of initial states on parameters are implemented - after that it's up to the user
- The main sim that could benefit from this is the
Simulationwith sensitivities enabled. See @frankiepe 's use case with a logistic model where one parameter affects both ODEs and an initial state
To do
- [x] Write up the test case as a technical note in https://github.com/myokit/myokit-examples
- [x] Work out what a good (intuitive, understandable, backwards compatible) simulation API would do
- [ ] Add tests based on above
- [ ] Implement
Proposal and draft test case at https://github.com/myokit/examples/blob/main/technical-notes/2-4-test-case-logistic-model.ipynb
@frankiepe @martinjrobins comments on the questions in the proposal section would be very welcome
Question 1: Alternatively, we could remove the ability to calculate dy/dy0 altogether, only allowing it via y0=f(p). This would make Myokit's language a bit simpler (remove the init keyword, remove the InitialValue expression that we introduced explicitly for this purpose). Would it make it harder to use though? Or would it make it conceptually more clear?
I'm always in favor of simplicity, so remove dy/dy0, there is no point if you can have parameters in initial conditions
Methods state() and set_state() use floats. Question 2: Should these methods set the s_state too? Or do we maintain separate methods for that?
s_state is separate from state so feels like it should have its own setter
Question 3: This means that the explicit dependence of s_state on initial states is lost. I think this is OK because you wouldn't pre-pace if part of your initial state was a parameter?
Agree this is OK, the user is explicitly setting new initial conditions
Question 4: It also means that you still have an implicit dependence on any initial state parameters from before you pre-paced. That makes less sense to me, because running a simulation now answers the question "how do my states/variables depend on the initial values at some long ago time now forgotten?" I.e. they are no longer the sensitivities w.r.t. the set initial_state. Maybe they should all just be zeroed instead? Or should we maintain the "pre paced" sensitivities w.r.t. parameters, but zero the ones for initial states?
I think sensititvies should be zeroed after the pre, by using pre you are constructing a new model (new initial conditions), so I think it makes sense that sensitivities are zeroed out.
Question 5: When I (1) Create a sim, (2) Set a parameter, (3) Run. Should I expect the evaluation of the initial_state to have been updated? With the mechanism above the answer would be no (state was set to evaluated-initial_state at creation). So a user would have to (1) Create a sim, (2) Set a constant, (3) Reset (thereby re-evaluating the initial state), (4) Run?
Perhaps you need a new way of setting initial parameters, because currently (2) seems to be aimed at altering parameters mid-simulation. Perhaps allow users to set initial parameters in the simulation constructor (and the reset method so you don't have to re-compile the sim when doing a parameter sweep)?
Thanks @martinjrobins ! I think I agree with your first 4 answers. Would like something simpler for the 5th but will have to wait and see what we come up with :D
@martinjrobins @frankiepe I've updated the test case doc again, and am interested to hear your thoughts (the important bit starts at "Requirements" and ends after "Use case 1": https://github.com/myokit/examples/blob/main/technical-notes/2-4-test-case-logistic-model.ipynb
In short, being clever in the simulation object causes problems:
- The
default_stateanddefault_s_stateare related, i.e. if x0 = p then this implies something about dx0/dp. This means that we need to make decisions about when the user is in charge of maintaining this relationship, and when the simulation class can take over - The simulation class can be clever about some, but not all initial state sensitivities. I.E. the user can specify the initial state using expressions, and this can be used to derive initial state sensitivities that depend on parameters used as independents. However, the remaining initial state sensitivities are non-zero in realistic contexts, and the user would have to do either (A) clever things or (B) a bit of pre-pacing to a steady or periodic state to set those. Both mean the benefits of having the simulation work stuff out symbolically are lost
- The rules for
set_constantbecome more complicated. E.g. if affects the initial state but only after calling reset but not if the user has explicitly set a different initial state or things like that
In light of those 3, I'm thinking we do clever stuff only when a simulation is created, resulting in a simulation that only has floats (no Expressions), and leaving the clever stuff to the user?
I definitely think “leaving clever stuff to the user” is the way to go here. I think my use case was quite particular and probably not something that would come up much for myokit users (?).
I agree with you both :) Lets have the Simulation be clever in the __init__, but after that the user is in charge of keeping everything consistent.
I didn't quite follow all that to be honest, but still holding out on being a proper myokit user! I agree that Frankie's use case was somewhat pathological in terms of setting weird times to get the state variable IC we wanted.
I think the only time I might expect the initial sensitivities to be set non-zero for me is if there is something like
set_initial_state_variables_to_steady_state_for(-80mV) (or some other more explicit function of parameters)
which I have in old matlab code. In which case you can do the sensitivity_ICs as well (at least, easy with autodiff).
Bit more clarification here: https://github.com/myokit/examples/blob/main/technical-notes/1-3b-cvodes-initial-values.ipynb