Skip to content

Complex levelling, per-complex relaxation and relax_enz_concs - #63

Merged
edkerk merged 5 commits into
developfrom
feat/level-complex-concs
Oct 17, 2026
Merged

edkerk merged 5 commits into
developfrom
feat/level-complex-concs

Conversation

@edkerk

@edkerk edkerk commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Adds three things for proteomics-constrained ecModels: one abundance per enzyme complex, relaxation of whole complexes, and relaxation of enzyme caps by the least total amount in one LP. All are opt-in; existing models and the tutorials are unchanged. Discussed in CompMetEng/raven-gecko-parity#131.

Why

Complex subunits are quantified unevenly. Absolute proteomics recovers the subunits of one complex unevenly. In the yeast chemostat data of SysBioChalmers/overflow, subunits of complex III that are present 1:1 come out up to tenfold apart, while their replicates agree within 2–9%. constrain_enz_concs caps every subunit on its own, so a complex can carry no more flux than its least abundant measurement allows.

Single-subunit relaxation scatters a complex. flexibilize_enz_concs raises the single most limiting subunit, so a complex ends up with subunits at unrelated levels. In that analysis, pyruvate dehydrogenase was capped by LAT1 in one condition and by PDA1 in another, because a different subunit had been raised in each. Whether the model had to secrete ethanol at that growth rate followed from which subunit that was.

Iterative relaxation depends on order. Which enzymes end up raised depends on the ranking signal and on the order in which enzymes become limiting. In the same analysis, changing only the ranking from gain per mg to gain per fold changed which fast-growth conditions were predicted to overflow.

What it adds

complex_families(model)

Groups the enzymes of every multi-subunit reaction in ec.rxn_enz_mat into complexes.

  • Merging variants: two enzyme sets are one complex when they share at least two subunits and at least half of the larger set. This covers isozyme variants that swap a subunit, and the same complex on several reactions.
  • Shared subunits: a protein in three or more complexes, in two with equal weight, or that also catalyses a reaction on its own is shared and keeps its own measurement. Examples are electron carriers (cytochrome c), thioredoxin and LPD1 (the E3 subunit shared by pyruvate dehydrogenase, 2-oxoglutarate dehydrogenase and the glycine cleavage system). Without this rule they would chain unrelated complexes together: cytochrome c alone joins complexes III and IV and five enzymes that use cytochrome c into one 29-subunit "complex".
  • Ownership: a subunit in two complexes belongs to the one whose enzyme sets catalyse more reactions. COX1–3 of complex IV also appear in an assembly reaction.

Returns a ComplexFamilies with members(enzyme) and owned(index).

level_complex_concs(model) / level_complex_abundances(model, ids, values)

Gives every complex one abundance: the median, over its measured owned subunits, of molar abundance / copies, written back × copies (× MW for ec.concs).

  • Unmeasured owned subunits are filled.
  • A zero measurement counts as unseen.
  • level_complex_concs works on ec.concs (mg/gDCW) between fill_enz_concs and constrain_enz_concs; level_complex_abundances works on molar data before it is written into the model.
  • statistic="mean" is available.

flexibilize_enz_concs(..., per_complex=True)

Treats every complex as one unit:

  • Ranking: units are ranked by the growth gained per fold of raising all their caps, Σ |reduced cost of usage_prot_i| × cap. Unlike the prot_<id> shadow price, this leaves out the protein pool's price, which would otherwise be counted once per subunit and favour large complexes. The sum is also the same for every optimal dual when a levelled complex binds on all subunits at once.
  • Fallback: when no dual singles a unit out (no duals, or the pool carries the whole price), each candidate is probed with its caps raised and the pool lifted.
  • Raising: all of a unit's measured subunits are raised by one factor, and a cap is never lowered.
  • Counting: iterations are counted per unit, and the refinement pass keeps one factor per complex.

relax_enz_concs(model, per_complex=False, weight="relative", pool_weight=1000.0)

The order-independent counterpart of flexibilize_enz_concs.

  • One LP: every measured cap becomes usage <= cap * (1 + x), the protein pool may grow by a fold too, and the sum of the x is minimised subject to growth >= exp_growth.
  • Weighting: weight="relative" minimises summed folds; "absolute" minimises the mg added.
  • Pool price: the pool's fold costs pool_weight times an enzyme's, so the pool grows only where raising enzymes cannot reach the target.
  • No lowering: folds count from the current caps, so a cap raised before is never lowered.
  • Per complex: with per_complex=True, a complex's measured subunits share one x.
  • Result: returns a FlexEnzResult. It raises ValueError when no relaxation reaches the target, because growth is then not limited by protein.

When it helps

  • Complex quantification: proteomics with uneven subunit recovery, where whole complexes rather than single proteins carry the constraint (respiratory chain, 2-oxo-acid dehydrogenases, ATP synthase, ribosome-associated machinery).
  • Comparing conditions: asking which enzyme limits in each condition, where the answer should not change because a different subunit happened to be raised.
  • Robustness: any analysis that depends on the caps left after relaxation, where it should not depend on the order of relaxation. relax_enz_concs gives one answer; comparing it with flexibilize_enz_concs shows which conclusions depend on the relaxation heuristic.

Use levelling and per-complex relaxation together. Per-complex relaxation on unlevelled subunits raises every subunit by the fold its worst-measured subunit needs, which inflates the well-measured ones.

Limitations

  • Stoichiometry: levelling is only as good as the copy numbers in ec.rxn_enz_mat. Where apply_complex_data found no exact ComplexPortal match, every subunit counts as one copy (raven-gecko-parity#120).
  • Shared subunits keep their single measurement although they serve several complexes, so they can still limit on their own.
  • Copy-number conflicts: when variants disagree on a subunit's copies, the first is used and a warning is logged.

Usage

from geckopy import (fill_enz_concs, level_complex_concs, constrain_enz_concs,
                     relax_enz_concs, flexibilize_enz_concs)

fill_enz_concs(model, prot_data)
level_complex_concs(model)                       # one abundance per complex
constrain_enz_concs(model)
result = relax_enz_concs(model, per_complex=True)  # or flexibilize_enz_concs(model, per_complex=True)

Tests and docs

  • tests/test_level_complex_concs.py: grouping, including isozyme variants, a single shared protein, a carrier, ownership by reaction count, a tie and an enzyme that also works alone; median levelling with stoichiometry, filling and mass units; per-complex flexibilization, including levelled subunits binding together, a binding pool and never lowering a cap.
  • tests/test_relax_enz_concs.py: least summed fold, no relaxation needed, pool only when needed, earlier raises kept, an unreachable target, per complex, weighting.
  • docs/relaxation_methods.md describes the new method and the complex options; CHANGELOG.md has the entries.
  • The full suite passes except the test modules that need the optional cma dependency (not installed in the test environment).

There is no MATLAB counterpart yet; ledger rows and parity scenarios are tracked in CompMetEng/raven-gecko-parity#133.

level_complex_concs (and level_complex_abundances for molar data) gives
every enzyme complex one abundance, the median per copy over its
measured subunits, so a single badly quantified subunit no longer sets
the complex's capacity. complex_families groups isozyme variants into
one complex and leaves subunits shared between complexes as measured.
flexibilize_enz_concs(per_complex=True) raises all subunits of a
limiting complex by the same factor. See raven-gecko-parity#131.
…ering caps

Two enzyme sets merge only when they share two or more subunits and half
of the larger set, so electron carriers no longer chain complexes
together; a subunit in two complexes belongs to the one with more
reactions. per_complex ranks complexes by summed control coefficients,
counts iterations per complex and never lowers a cap.
@edkerk
edkerk merged commit 9938784 into develop Oct 17, 2026
5 checks passed
@edkerk
edkerk deleted the feat/level-complex-concs branch October 17, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant