feat(gradient): route free params that reach the model through a condition (#511) - #513
Merged
Merged
Conversation
…ition (#511) An edition-2 gradient fit aborted whenever a free parameter reached the model only through a `condition:` parameter-reference perturbation (a per-condition estimated initial condition, ADR-0076) -- `route_experiment` refused any `is_param_ref` mutation, so the fit fell back to a gradient-free optimizer. That is a common PEtab pattern, so problems like Bruno_JExpBot2016 silently lost the gradient path entirely. Compose the chain rule instead of refusing. A condition assignment `target = free_param` now gives the *referenced* free parameter a RouteContribution on the target's own sensitivity column: the IC axis when the target bares a species initial value (d(IC)/d(target) = 1), the parameter axis for an ordinary global such as a shared rate multiplier. Because one free parameter may be assigned to several targets in a single condition (Bruno's `szea` drives six rate multipliers at once), a ParamRoute becomes a *sum* over contributions rather than a single (target, key, factor). Both assembly accessors -- objective and constraint -- sum them, so the gradient, the residual Jacobian and the EFIM/Fisher block all follow. Two supporting changes: * both backends expose a bare-initializer seed map (`{model param -> species}`) through `sensitivity_entity_namespace`, which now returns a 3-tuple, keeping the routing core backend-agnostic; and * `_setup_gradient_path` applies the UNION request over the wildtype and every scored condition (`apply_routings`). The wildtype alone is not a superset once a condition routes a free parameter to a column no other experiment binds -- an extra requested column is harmless, a missing one aborts the assembly. Correctness boundaries stay honest refusals rather than silently-zero columns: a seed whose d(IC)/d(param) is not a plain 1 (non-bare initialAssignment, an amount species needing a non-unit concentration factor, or a parameter seeding several species), a non-'=' parameter reference, and a condition target that binds no sensitivity entity. Validated on the real Bruno_JExpBot2016 job: the assembled gradient matches central finite differences to 2.2e-07 across all 13 free parameters, and a gntr fit now solves the problem (optimality gap 1.1e-05 against the Grein et al. reference J*, threshold 1.92), recovering the PEtab nominal parameter vector to 1.8e-06 -- a problem that previously could not use the gradient path at all. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #511.
Problem
An edition-2 gradient fit aborted whenever a free parameter reached the model only through a
condition:parameter-reference perturbation (a per-condition estimated initial condition, ADR-0076).route_experimentrefused anyis_param_refmutation, so the fit fell back to a gradient-free optimizer:That is a common PEtab pattern, so problems like
Bruno_JExpBot2016silently lost the gradient path entirely.Approach
Compose the chain rule instead of refusing. A condition assignment
target = free_paramgives the referenced free parameter aRouteContributionon the target's own sensitivity column — the IC axis when the target bares a species initial value (d(IC)/d(target) = 1), the parameter axis for an ordinary global such as a shared rate multiplier.The structural piece the issue's suggested direction did not anticipate: one free parameter may be assigned to several targets in a single condition (Bruno's
szeadrives six rate multipliers at once), so its derivative is a sum of native columns.ParamRoutetherefore becomes a list of contributions rather than a single(target, key, factor), and both assembly accessors — objective and constraint — sum them, so the gradient, residual Jacobian and EFIM/Fisher block all follow.Supporting changes:
{model param -> species}) viasensitivity_entity_namespace, which now returns a 3-tuple — keeping the routing core backend-agnostic._setup_gradient_pathapplies the union request over the wildtype and every scored condition (apply_routings). The wildtype alone is not a superset once a condition routes a free parameter to a column no other experiment binds; an extra requested column is harmless, a missing one aborts the assembly.Boundaries kept as honest refusals
Rather than emitting a silently-zero or wrong column,
GradientNotSupportedis still raised for a seed whosed(IC)/d(param)is not a plain 1 (non-bareinitialAssignment, an amount species needing a non-unit concentration factor, or a parameter seeding several species), a non-=parameter reference, and a condition target that binds no sensitivity entity.Validation
Against the real
Bruno_JExpBot2016job (13 free params on log10 scale, 6 conditions):gntrfit vs Grein et al. referenceJ*(threshold 1.92)nominalValuevectorThe FD oracle covers both composed axes — the
initialAssignment-seeded IC andszea's six-way multiplier sum. A problem that previously could not use the gradient path at all now solves it essentially exactly, andJ*was independently confirmed againstbest_fx_marvin.csv.Tests
New coverage for: IC and parameter composition, the multi-target sum, all three refusal paths,
classify_condition_target, the union request (apply_routings), constraint + Fisher blocks with summed routes, non-unit condition factors composed with a param-ref, net-backend composition end-to-end, and SBML seed-map exposure incl. the non-bare and amount-species refusals. Plus an FD oracle on a synthetic mini-Bruno.Notes
sensitivity_entity_namespacereturning a 3-tuple is a breaking change for any external caller; all in-tree call sites are updated.🤖 Generated with Claude Code