-
Notifications
You must be signed in to change notification settings - Fork 31
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
Adjoint sensitivities, fix #876 #917
Conversation
Let both methods have all parameters required by model specified implementation similarily as it is done for fJ and fJSparse methods.
The NewtonSolver contains a prepareLinearSystemB method that retrieves and passes the Jacobian JB. This allows solving linear systems JB*x = rhs.
When a linear system is prepared construct a Jacobian and use it directly in matrix-vector multiplication instead of mutliple calls to Model::fJv.
Avoid backwards integration to compute sensitivities when handled with a steady-state solution. It is enough to solve a linear system.
This effectively takes away the possibility to compute steady-state adjoint sensitivities for systems with conservation laws, i.e., where the jacobian is singular. I would prefer if this was an optional feature. |
Sure. I was thinking of adding an additional flag to the solver, the same way the preequilibration is set, with the feature being switched off by default. Do you have in mind a suitable name for it you would prefer? |
One could, in a second step, add an exception-handling, like this is done in forward-steady-state computation:
Then, option 2 would just replace option 1 and still be working for singular Jacobians. Would that be a suitable next step? (Not for this PR, but for a later one)? I think we can save quite some time by this shortcut. We have some applications of this form now, mostly large-scale systems. And the steady-state case has simply quite some structure, which can still be exploited... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall. Thanks for implementing, but some changes still have to be done.
The flag to switch on/off the new functionality would be nice... Something like NewtonSolverBackward -> true/false might do it...
src/backwardproblem.cpp
Outdated
solver->runB(model->t0()); | ||
solver->writeSolutionB(&t, xB, dxB, xQB, this->which); | ||
solver->getDiagnosisB(0, rdata, this->which); | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this shouldn't be an else
here: If we put an else
, this means that we can have either steady-state or time-course data, but not both (which would ideally be possible).
Suggestion: keep the if above, and check for
(it > 0 && model->getTimepoint(it - 1) > model->t0())
instead of the else
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember we talked about it. I am not convinced you can do that or I am missing something. To integrate backwards from t_{N-1} point you need an initial condition and you do not know what the value of p at this point is. You only have its value at t_N.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value of p at that point is zero before the update, and at the point it gets updated by dJydx, as usual.
Imagine we integrate not from steadystate but a "very late" timepoint: whatever p was there, the time interval from the "very late" to the prelast timepoint is "almost infinte". So enough time for p to equilibrate to its steady-state, which is (necessarily) at 0.
src/backwardproblem.cpp
Outdated
@@ -145,6 +155,52 @@ realtype BackwardProblem::getTnext(std::vector<realtype> const& troot, | |||
return rdata->ts[it]; | |||
} | |||
|
|||
void BackwardProblem::computeIntegralForSteadyState() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd personally like to have this routine as part of steadystateproblem.cpp rather than backwardproblem.cpp,
maybe as workSteadyStateProblemBackward(). This way, we don't need to include newton_solver.h and keep everything which is not somehow concerned with usual ODE-integration out of backwardproblem.
src/backwardproblem.cpp
Outdated
for (int ip=0; ip<model->nplist(); ++ip) | ||
{ | ||
if (model->ndxdotdp_explicit > 0) { | ||
auto col = model->dxdotdp_explicit.indexptrs(); | ||
auto row = model->dxdotdp_explicit.indexvals(); | ||
auto data_ptr = model->dxdotdp_explicit.data(); | ||
for (sunindextype iCol = col[model->plist(ip)]; | ||
iCol < col[model->plist(ip) + 1]; ++iCol) | ||
xQB[ip] += xB[row[iCol]] * data_ptr[iCol]; | ||
} | ||
|
||
if (model->ndxdotdp_implicit > 0) { | ||
auto col = model->dxdotdp_implicit.indexptrs(); | ||
auto row = model->dxdotdp_implicit.indexvals(); | ||
auto data_ptr = model->dxdotdp_implicit.data(); | ||
for (sunindextype iCol = col[model->plist(ip)]; | ||
iCol < col[model->plist(ip) + 1]; ++iCol) | ||
xQB[ip] += xB[row[iCol]] * data_ptr[iCol]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified.
if (model->ndxdotdp_explicit > 0)
model->dxdotdp_explicit.multiply(xQB, xB, plist_, False);
if (model->ndxdotdp_implicit > 0)
model->dxdotdp_implicit.multiply(xQB, xB, plist_, false);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely.
src/backwardproblem.cpp
Outdated
else | ||
{ | ||
for (int ip=0; ip<model->nplist(); ++ip) | ||
xQB[ip] = N_VDotProd(xB.getNVector(), model->dxdotdp.getNVector(ip)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
include/amici/backwardproblem.h
Outdated
* | ||
* Valid only if the computed solution is a steady-state. | ||
*/ | ||
void computeIntegralForSteadyState(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally move to steadystateproblem.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check it out. I need ExpData to generate the rhs of the problem or pass dJydy from the forward problem. That is why I place that in backwardproblem.h. The right-hand side of the problem is constructed for free in handleDataPointB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point!
Yet, philosophy-wise, I'd prefer to include ExpData into SteadyStateProblem rather than mixing the steadystate and the adjoint part...
* @param xBdot Vector with the adjoint right hand side (unused) | ||
* @param JB dense matrix to which values of the jacobian will be written | ||
*/ | ||
virtual void fJB(const realtype t, realtype cj, const AmiVector &x, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is what causes the CI errors...
Not yet sure what the problem is precisely. Will have a closer look tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done @paszkow . If you accept my pull request into your fork, then the CI errors should be fixed now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I will. I already have all your comments implemented. Need to double-check the correctness of the results I am getting before pushing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great if you could add these tests as unittests for the proposed, if you need help with that, please let me know!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good idea. Will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I will. I already have all your comments implemented. Need to double-check the correctness of the results I am getting before pushing.
At least for the small steady-state example problem, things looked correct when I tested them...
Codecov Report
@@ Coverage Diff @@
## develop #917 +/- ##
===========================================
- Coverage 71.85% 65.62% -6.24%
===========================================
Files 52 17 -35
Lines 8431 2906 -5525
===========================================
- Hits 6058 1907 -4151
+ Misses 2373 999 -1374
Continue to review full report at Codecov.
|
solver->runB(model->t0()); | ||
solver->writeSolutionB(&t, xB, dxB, xQB, this->which); | ||
solver->getDiagnosisB(0, rdata, this->which); | ||
if (it>=0 && model->getTimepoint(it) > model->t0()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does model->getTimepoint(it)
do if it == -1? Does it return false?
It boils down to some amivector.at(-1)
, which should be failsafe...
You have tested this, @paszkow ? If it works, I'm fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The timepoints are stored in std::vector which at method accepts values of size_t type. I am not a fan of implicit conversions because you never know what may happen. Safer to keep it this way.
xQB[ip] = N_VDotProd(rhs.getNVector(), model->dxdotdp.getNVector(ip)); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks nice!
Really thanks for your contribution here, @paszkow ! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good!
I would like to double-check stuff. We should also implement some tests... Beyond that, I'm very happy...
Concerning the unittests that need to be written. I have two questions.
|
Hi
If I get you correct, it makes perfect sense: The gradient is summed over timepoints. Hence, if you have timepoints at 1e6 and 1e8, you have twice as many (noisy) timepoints as if you only use 1e8.
Yep... does that happen if |
Exactly with this setting |
Yes, now it is clear. I missed that. |
Still, I don't really get it: |
Already clarified thanks to @paulstapor. |
Here is an overview of what got changed by this pull request: Clones added
============
- src/newton_solver.cpp 2
- include/amici/model_dae.h 2
- include/amici/model_ode.h 2
- src/steadystateproblem.cpp 2
See the complete overview on Codacy |
Computing adjoint sensitivities by solving a linear system for the steady-state solution of the ode problem.