diff --git a/ecoli/processes/metabolism.py b/ecoli/processes/metabolism.py index 4a1c653f5..44c01b761 100644 --- a/ecoli/processes/metabolism.py +++ b/ecoli/processes/metabolism.py @@ -95,6 +95,7 @@ class Metabolism(Step): "linked_metabolites": None, "aa_exchange_names": [], "removed_aa_uptake": [], + "set_reaction_bounds": {}, # In form: {FBA_RXN_ID:[lb,ub]} (reaction must match FBA basis) "seed": 0, # TODO: For testing, remove later (perhaps after modifying sim data) "reduce_murein_objective": False, @@ -532,6 +533,27 @@ def next_update(self, timestep, states): catalyst_counts, counts_to_molar, coefficient, translation_gtp ) + # Set reaction limits from config options + config_reaction_bounds = self.parameters.get("set_reaction_bounds", {}) + for rxn, bounds in config_reaction_bounds.items(): + if rxn not in self.fba_reaction_ids: + raise ValueError( + f"set_reaction_bounds: reaction '{rxn}' not found in FBA reaction IDs" + ) + try: + lower_bound, upper_bound = bounds + except (TypeError, ValueError) as e: + raise ValueError( + f"set_reaction_bounds for '{rxn}' must be a 2-item sequence [lower, upper], got: {bounds!r}" + ) from e + if lower_bound > upper_bound: + raise ValueError( + f"set_reaction_bounds for '{rxn}' has lower_bound > upper_bound ({lower_bound} > {upper_bound})" + ) + self.model.fba.setReactionFluxBounds( + rxn, lowerBounds=lower_bound, upperBounds=upper_bound + ) + # Constrain reactions based on targets targets, upper_targets, lower_targets = self.model.set_reaction_targets( kinetic_enzyme_counts, @@ -1149,5 +1171,31 @@ def test_metabolism_listener(): assert isinstance(reaction_fluxes[1], list) +def test_set_reaction_bounds_config(): + from ecoli.experiments.ecoli_master_sim import EcoliSim + import pytest + + sim = EcoliSim.from_file() + sim.max_duration = 2 + sim.build_ecoli() + + metabolism = sim.ecoli.processes["agents"]["0"]["ecoli-metabolism"] + + # test invalid configuration: reaction not in FBA reaction IDs + metabolism.parameters["set_reaction_bounds"] = {"NOT-A-REAL-RXN": [0, 1]} + with pytest.raises(ValueError, match="not found in FBA reaction IDs"): + sim.run() + + # test valid configuration + rxn_id = metabolism.fba_reaction_ids[0] + metabolism.parameters["set_reaction_bounds"] = {rxn_id: [0.0, 0.0]} + sim.run() + data = sim.query() + reaction_fluxes = data["agents"]["0"]["listeners"]["fba_results"]["reaction_fluxes"] + rxn_idx = metabolism.fba_reaction_ids.index(rxn_id) + for fluxes_at_t in reaction_fluxes: + assert fluxes_at_t[rxn_idx] == pytest.approx(0.0, abs=1e-6) + + if __name__ == "__main__": test_metabolism_listener()