Repository navigation
Conversation
MSModelUtil.test_solution knocks out each gapfilled reaction and keeps it only if the objective drops below the solution's minimum objective. When the knockout makes the problem infeasible, slim_optimize returns NaN, and NaN < minobjective is False, so the reaction was treated as not needed. With keep_changes=True (as in MSGapfill.integrate_gapfill_solution) it was then removed from the model and from the solution. Treat a NaN objective (infeasible or otherwise not optimal) as needed. Test: e_coli_core without GLCpts_c0 (ATPM has a lower bound, so the knockout is infeasible); integrate_gapfill_solution now keeps GLCpts_c0 and the model grows on its gapfill medium. Co-Authored-By: Claude Opus 5.5 <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.
Why this matters in our workflow
We re-gap-fill models and add the results with
MSGapfill.integrate_gapfill_solution. That method callsMSModelUtil.test_solution(solution, keep_changes=True)to remove gap-filled reactions that are "not needed". Ifknocking out a reaction makes the LP infeasible, the reaction is treated as not needed and deleted, although the model
cannot work without it. This happens when the model has a forced flux, such as a non-zero ATP maintenance lower bound
(as in
e_coli_core), or when the knock-out LP fails for any other reason.What happens
msmodelutl.pyL541-L606 at 062714b:slim_optimize()returns NaN for any non-optimal status (its defaulterror_value). ThenNaN < minobjectiveisFalse, so the reaction counts as not needed. Withkeep_changes=True, which is whatMSGapfill.integrate_gapfill_solutionpasses at L369,the reaction is removed from the model and from the solution. The model then cannot grow on the medium it was
gap-filled on. The
"<"branch at L583 has the same problem.How to reproduce
Use the package's test fixtures.
e_coli_corewithoutGLCpts_c0is gap-filled on glucose withtemplate_core_bigg.json. ItsATPM_c0has a lower bound of 8.39, so without glucose uptake the problem is infeasible.The fix
The change is the same in both directions. An infeasible knock-out means the model cannot meet its constraints without
the reaction, so the reaction is needed. I used an explicit
math.isnaninstead ofslim_optimize(error_value=0),because with
error_value=0an infeasible knock-out would still count as not needed wheneverminobjectiveis 0,which
MSGapfill(minimum_obj=0)allows.Callers.
test_solutionis called only fromMSGapfill.integrate_gapfill_solution(msgapfill.py:369), whichis called from
MSATPCorrection.apply_growth_media_gapfilling(msatpcorrection.py:423),MSGapfill.gapfill(
msgapfill.py:523) andMSCommunity(mscommunity.py:651). For each of them, the only change is that reactionswhose knock-out is infeasible stay in the model. A feasible knock-out behaves exactly as before.
Not changed here. With
keep_changes=True, a "reversed" reaction that is found not needed is removed from themodel entirely (
tempmodel.remove_reactions(removed_rxns)), although it was in the model before the gap-fill.Restoring its original bound may be the intended behaviour. That is a separate question, so this PR leaves it alone.
Tests
tests/core/test_msgapfill.py::test_integrate_gapfill_solution_keeps_reaction_with_infeasible_knockoutis thereproduction above. It checks that
GLCpts_c0stays in the model and in the solution, and that the model grows abovethe gap-fill minimum.
Against dev's code:
With the fix, it passes. Full suite (Python 3.10, cobra 0.32.1, CPLEX 22.1.1):
tests/coregoes from 56 to 57passed, and
tests/from 58 to 59.black22.8.0 and the other pre-commit hooks pass.Real-model check
We took a genome-scale E. coli K-12 model built with
MSBuilder.build()(RAST annotations, GramNeg template v7.1).We removed its only glucose uptake reaction,
rxn05226_c0(D-glucose transport via PEP:Pyr PTS), re-gap-filled onminimal glucose with
MSGapfill(model, [template], default_target="bio1"), and integrated withintegrate_gapfill_solution:ATPM_c0lower boundrxn05226_c0rxn05226_c0Two full
MSBuilder.build()runs (E. coli K-12 and a Pantoea strain) gave identical models on dev and on thisbranch. In those builds
test_solutionis never reached, because the ATP correction integrated no core gap-fill.Related
🤖 Generated with Claude Code