Skip to content

A free parameter that binds a model id AND seeds another entity gets a silently-zero gradient column (#530 sibling) #534

Description

@wshlavacek

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions