55import numpy as np
66import numpy .typing as npt
77import time
8+ import pytest
89from unum import Unum
910import warnings
1011from scipy .sparse import csr_matrix
4748 "R15-RXN-MET/CPD-479//CPD-479/MET.25." ,
4849 "TRANS-RXN-218" ,
4950 "TRANS-RXN0-601-PROTON//PROTON.15. (reverse)" ,
50- "DISULFOXRED-RXN[CCO-PERI-BAC]-MONOMER0-4152/MONOMER0-4438//MONOMER0-4438/MONOMER0-4152.71."
51+ "DISULFOXRED-RXN[CCO-PERI-BAC]-MONOMER0-4152/MONOMER0-4438//MONOMER0-4438/MONOMER0-4152.71." # Commented on Heena's branch?
5152 "DEPHOSICITDEHASE-RXN" ,
5253 "PHOSICITDEHASE-RXN" ,
5354 "GLYCOLALD-DEHYDROG-RXN" ,
6364 "RXN-22461" ,
6465 "RXN-22462" ,
6566 "RXN-22463" ,
67+ "PYRROLINECARBDEHYDROG-RXN" ,
68+ "RXN0-7008-PRO/UBIQUINONE-8//L-DELTA1-PYRROLINE_5-CARBOXYLATE/CPD-9956/PROTON.67." ,
69+ "GLUCOKIN-RXN-GLC/ATP//ALPHA-GLC-6-P/ADP/PROTON.34." , # gets confused with PTS
70+ "PRPPSYN-RXN-CPD-15318/ATP//PRPP/AMP/PROTON.31." , # duplicate
71+ "TRANS-RXN0-574-GLC//GLC.9." , # duplicate
72+ "GLUCOKIN-RXN-GLC/ATP//D-glucopyranose-6-phosphate/ADP/PROTON.48." , # duplicate
6673]
6774
6875# not key central carbon met
@@ -134,6 +141,12 @@ class MetabolismReduxClassic(Step):
134141 "cell_density" : 1100 * units .g / units .L ,
135142 "concentration_updates" : None ,
136143 "maintenance_reaction" : {},
144+ "objective_weights" : {
145+ "secretion" : 0.01 ,
146+ "efficiency" : 0.000001 ,
147+ "kinetics" : 0.0000001 ,
148+ "homeostatic" : 1 ,
149+ },
137150 }
138151
139152 def __init__ (self , parameters ):
@@ -293,7 +306,6 @@ def ports_schema(self):
293306 "estimated_exchange_dmdt" : {},
294307 "estimated_intermediate_dmdt" : [],
295308 "target_kinetic_fluxes" : [],
296- "target_kinetic_bounds" : [],
297309 "reaction_catalyst_counts" : [],
298310 "maintenance_target" : 0 ,
299311 }
@@ -454,14 +466,11 @@ def next_update(self, timestep, states):
454466 target_kinetic_values = enzyme_kinetic_boundaries [:, 1 ]
455467 target_kinetic_bounds = enzyme_kinetic_boundaries [:, [0 , 2 ]]
456468
457- # TODO (Cyrus) solve network flow problem to get fluxes
458- objective_weights = {
459- "secretion" : 0.01 ,
460- "efficiency" : 0.000001 ,
461- "kinetics" : 0.0000001 ,
462- }
469+ objective_weights = self .parameters ["objective_weights" ]
470+
463471 solution : FlowResult = self .network_flow_model .solve (
464- homeostatic_targets = target_homeostatic_dmdt ,
472+ homeostatic_concs = homeostatic_metabolite_concentrations ,
473+ homeostatic_dm_targets = target_homeostatic_dmdt ,
465474 maintenance_target = maintenance_target ,
466475 kinetic_targets = target_kinetic_values ,
467476 binary_kinetic_idx = binary_kinetic_idx ,
@@ -507,7 +516,6 @@ def next_update(self, timestep, states):
507516 "estimated_homeostatic_dmdt" : estimated_homeostatic_dmdt ,
508517 "target_homeostatic_dmdt" : target_homeostatic_dmdt ,
509518 "target_kinetic_fluxes" : target_kinetic_flux ,
510- "target_kinetic_bounds" : target_kinetic_bounds ,
511519 "estimated_exchange_dmdt" : estimated_exchange_dmdt ,
512520 "estimated_intermediate_dmdt" : estimated_intermediate_dmdt ,
513521 "maintenance_target" : target_maintenance_flux ,
@@ -634,7 +642,8 @@ def set_up_exchanges(self, exchanges: set[str], uptakes: set[str]):
634642
635643 def solve (
636644 self ,
637- homeostatic_targets : Optional [Iterable [float ]] = None ,
645+ homeostatic_concs : Iterable [float ],
646+ homeostatic_dm_targets : Iterable [float ],
638647 maintenance_target : float = 0 ,
639648 kinetic_targets : Optional [Iterable [float ]] = None ,
640649 binary_kinetic_idx : Optional [Iterable [int ]] = None ,
@@ -658,29 +667,36 @@ def solve(
658667 if self .maintenance_idx is not None :
659668 constr .append (v [self .maintenance_idx ] == maintenance_target )
660669 # If enzymes not present, constrain rxn flux to 0
661- if binary_kinetic_idx :
670+ if binary_kinetic_idx is not None :
662671 constr .append (v [binary_kinetic_idx ] == 0 )
663672
664673 constr .extend ([v >= 0 , v <= upper_flux_bound , e >= 0 , e <= upper_flux_bound ])
665674
666675 loss = 0
667- loss += cp .norm1 (dm [self .homeostatic_idx ] - homeostatic_targets )
676+ # Must normalize by metabolite concentrations to prevent negative counts.
677+ homeostatic_term = cp .norm1 (
678+ (dm [self .homeostatic_idx ] - homeostatic_dm_targets ) / homeostatic_concs
679+ )
680+ loss += (
681+ objective_weights ["homeostatic" ] * homeostatic_term
682+ if "homeostatic" in objective_weights
683+ else 0
684+ )
668685 loss += (
669686 objective_weights ["secretion" ] * (cp .sum (e [self .secretion_idx ]))
670687 if "secretion" in objective_weights
671- else loss
688+ else 0
672689 )
673690 loss += (
674691 objective_weights ["efficiency" ] * (cp .sum (v ))
675692 if "efficiency" in objective_weights
676- else loss
693+ else 0
677694 )
678- loss = (
679- loss
680- + objective_weights ["kinetics" ]
695+ loss += (
696+ objective_weights ["kinetics" ]
681697 * cp .norm1 (v [self .kinetic_rxn_idx ] - kinetic_targets )
682698 if "kinetics" in objective_weights
683- else loss
699+ else 0
684700 )
685701
686702 p = cp .Problem (cp .Minimize (loss ), constr )
@@ -726,8 +742,9 @@ def test_network_flow_model():
726742 model .set_up_exchanges (exchanges = exchanges , uptakes = uptakes )
727743
728744 solution : FlowResult = model .solve (
729- homeostatic_targets = list (homeostatic_metabolites .values ()),
730- objective_weights = {"secretion" : 0.01 , "efficiency" : 0.0001 },
745+ homeostatic_concs = [0.1 ],
746+ homeostatic_dm_targets = list (homeostatic_metabolites .values ()),
747+ objective_weights = {"homeostatic" : 1 , "secretion" : 0.01 , "efficiency" : 0.0001 },
731748 upper_flux_bound = 100 ,
732749 solver = cp .GLOP ,
733750 )
@@ -737,7 +754,70 @@ def test_network_flow_model():
737754 )
738755
739756
740- # TODO (Cyrus) Add test for entire process
757+ @pytest .fixture
758+ def temp_config_dir (tmp_path ):
759+ """Create a temporary directory for test configs."""
760+ return tmp_path
761+
762+
763+ def test_redux_classic (temp_config_dir ):
764+ import json
765+ from ecoli .experiments .ecoli_master_sim import EcoliSim
766+
767+ config = {
768+ "experiment_id" : "metabolism_redux" ,
769+ "fail_at_max_duration" : False ,
770+ "max_duration" : 10 ,
771+ "progress_bar" : True ,
772+ "emitter" : "timeseries" ,
773+ "fixed_media" : "minimal" ,
774+ "condition" : "basal" ,
775+ "swap_processes" : {"ecoli-metabolism" : "ecoli-metabolism-redux-classic" },
776+ "exclude_processes" : ["exchange_data" ],
777+ "flow" : {
778+ "ecoli-metabolism-redux-classic" : [["ecoli-chromosome-structure" ]],
779+ "ecoli-mass-listener" : [["ecoli-metabolism-redux-classic" ]],
780+ "RNA_counts_listener" : [["ecoli-metabolism-redux-classic" ]],
781+ "rna_synth_prob_listener" : [["ecoli-metabolism-redux-classic" ]],
782+ "monomer_counts_listener" : [["ecoli-metabolism-redux-classic" ]],
783+ "dna_supercoiling_listener" : [["ecoli-metabolism-redux-classic" ]],
784+ "replication_data_listener" : [["ecoli-metabolism-redux-classic" ]],
785+ "rnap_data_listener" : [["ecoli-metabolism-redux-classic" ]],
786+ "unique_molecule_counts" : [["ecoli-metabolism-redux-classic" ]],
787+ "ribosome_data_listener" : [["ecoli-metabolism-redux-classic" ]],
788+ },
789+ "raw_output" : False ,
790+ "operons" : True ,
791+ "trna_charging" : False ,
792+ "ppgpp_regulation" : False ,
793+ "initial_state_gaussian" : True ,
794+ "trna_attenuation" : False ,
795+ "variable_elongation_transcription" : True ,
796+ "variable_elongation_translation" : False ,
797+ "mechanistic_translation_supply" : False ,
798+ "mechanistic_aa_transport" : False ,
799+ "translation_supply" : False ,
800+ "aa_supply_in_charging" : False ,
801+ "adjust_timestep_for_charging" : False ,
802+ "disable_ppgpp_elongation_inhibition" : True ,
803+ }
804+
805+ config_path = temp_config_dir / "test_metabolism_redux_classic.json"
806+ with open (config_path , "w" ) as f :
807+ json .dump (config , f )
808+
809+ sim = EcoliSim .from_file (config_path )
810+ sim .build_ecoli ()
811+ sim .run ()
812+
813+ data = sim .query ()
814+ assert data is not None
815+
741816
742817if __name__ == "__main__" :
818+ from tempfile import TemporaryDirectory
819+ from pathlib import Path
820+
743821 test_network_flow_model ()
822+ with TemporaryDirectory () as tmp_path :
823+ test_redux_classic (Path (tmp_path ))
0 commit comments