Summary
#530 (ADR-0095) taught the router that a condition target reaches the trajectory through
everything it seeds, carrying each d(entity)/d(target). The bind-by-id path did not get the
same treatment: route_experiment classifies a free parameter with classify_free_param, which
returns one column and never consults the seed map.
When that free parameter is itself only an input to an initialAssignment — it never appears in
the ODE right-hand side — its own sensitivity axis is identically zero, so the assembled column is
silently zero. ADR-0095 listed this as out of scope, but "out of scope" here means a wrong
number rather than a refusal, which is the failure mode that ADR is otherwise built to avoid.
Reproduction
Laske_PLOSComputBiol2019 (Benchmark-Models-PEtab) is a COPASI export in which every rate law reads
a ModelValue_* alias fixed by an initialAssignment, and no source name appears in a rate law:
initialAssignment: ModelValue_79 = k_syn_R_M # k_syn_R_M is a FREE parameter
The seed map already carries the right term; the route does not:
seed map entry for k_syn_R_M: (SeedTerm(target='param', key='ModelValue_79', node=('num', 1.0)),)
route for k_syn_R_M (a free parameter bound BY ID):
RouteContribution(target='param', key='k_syn_R_M', factor=1.0, node=None)
is ModelValue_79 in the request? False
Central differences against the assembled gradient at that problem's PEtab nominal point, over all
13 free parameters (the other 12 agree to 1e-3..1e-11; the small-gradient ones wobble with the step
size as expected on a stiff model):
param assembled central diff rel err
k_syn_R_M 0 -10.5047 1.00e+00 <-- h=1e-6
k_syn_R_M 0 -10.4588 1.00e+00 <-- h=1e-5
k_syn_R_M 0 -10.42 1.00e+00 <-- h=1e-7
Stable under every step size, so it is structural rather than FD noise.
This was masked before #531: the forward model did not track ModelValue_79 either, so a zero
gradient was consistent with an inert parameter. Fixing the forward model exposed the routing gap.
Fix
Give a bind-by-id free parameter the same classification a condition target gets: its own axis
plus one term per entity whose initial value it seeds, each with its own derivative, and no
own-axis term when it is a pure initial-value seed (the existing IC-precedence rule, which is that
same rule as a special case). The seed map and the derivative machinery already exist from #530;
only the by-id branch of route_experiment needs to consult them.
Two behaviours change, both toward correctness:
A free parameter that binds a model id and seeds nothing is unaffected, which is nearly all of them.
Summary
#530 (ADR-0095) taught the router that a condition target reaches the trajectory through
everything it seeds, carrying each
d(entity)/d(target). The bind-by-id path did not get thesame treatment:
route_experimentclassifies a free parameter withclassify_free_param, whichreturns one column and never consults the seed map.
When that free parameter is itself only an input to an
initialAssignment— it never appears inthe ODE right-hand side — its own sensitivity axis is identically zero, so the assembled column is
silently zero. ADR-0095 listed this as out of scope, but "out of scope" here means a wrong
number rather than a refusal, which is the failure mode that ADR is otherwise built to avoid.
Reproduction
Laske_PLOSComputBiol2019(Benchmark-Models-PEtab) is a COPASI export in which every rate law readsa
ModelValue_*alias fixed by aninitialAssignment, and no source name appears in a rate law:The seed map already carries the right term; the route does not:
Central differences against the assembled gradient at that problem's PEtab nominal point, over all
13 free parameters (the other 12 agree to 1e-3..1e-11; the small-gradient ones wobble with the step
size as expected on a stiff model):
Stable under every step size, so it is structural rather than FD noise.
This was masked before #531: the forward model did not track
ModelValue_79either, so a zerogradient was consistent with an inert parameter. Fixing the forward model exposed the routing gap.
Fix
Give a bind-by-id free parameter the same classification a condition target gets: its own axis
plus one term per entity whose initial value it seeds, each with its own derivative, and no
own-axis term when it is a pure initial-value seed (the existing IC-precedence rule, which is that
same rule as a special case). The seed map and the derivative machinery already exist from #530;
only the by-id branch of
route_experimentneeds to consult them.Two behaviours change, both toward correctness:
species <- 2*S0) currently falls through toits own parameter axis (a zero column when it is not in the RHS); it should route to the IC axis
with derivative 2.
classify_free_param's "Cut-1" comment names this as a deferred layer — Extend ADR-0076 condition routing to a non-unit seed derivative (d(IC)/d(target) != 1) -- Bertozzi_PNAS2020 #530is that layer.
should sum over all of them.
A free parameter that binds a model id and seeds nothing is unaffected, which is nearly all of them.