def make_local_species_population(self, retain_history=True): """ Create a :obj:`LocalSpeciesPopulation` that contains all the species in a model Instantiate a :obj:`LocalSpeciesPopulation` as the centralized store of a model's species population. Args: retain_history (:obj:`bool`, optional): whether the :obj:`LocalSpeciesPopulation` should retain species population history Returns: :obj:`LocalSpeciesPopulation`: a :obj:`LocalSpeciesPopulation` for the model """ molecular_weights = self.molecular_weights_for_species() # Species used by continuous time submodels (like DFBA and ODE) need initial population slopes # which indicate that the species is modeled by a continuous time submodel. # TODO(Arthur): support non-zero initial population slopes; calculate them with initial runs of dFBA and ODE submodels init_pop_slopes = {} for submodel in self.model.get_submodels(): if submodel.id in self.skipped_submodels(): continue if are_terms_equivalent(submodel.framework, onto['WC:ordinary_differential_equations']) or \ are_terms_equivalent(submodel.framework, onto['WC:dynamic_flux_balance_analysis']): for species in submodel.get_children(kind='submodel', __type=Species): init_pop_slopes[species.id] = 0.0 return LocalSpeciesPopulation( 'LSP_' + self.model.id, self.init_populations, molecular_weights, initial_population_slopes=init_pop_slopes, random_state=self.random_state, retain_history=retain_history)
def __init__(self, dynamic_model, random_state, wc_lang_compartment, species_ids=None): """ Initialize the volume and density of this :obj:`DynamicCompartment`\ . Args: dynamic_model (:obj:`DynamicModel`): the simulation's dynamic model random_state (:obj:`numpy.random.RandomState`): a random state wc_lang_compartment (:obj:`Compartment`): the corresponding static `wc_lang` `Compartment` species_ids (:obj:`list` of :obj:`str`, optional): the IDs of the species stored in this compartment Raises: :obj:`MultialgorithmError`: if `self.init_volume` or `self.init_density` are not positive numbers """ super(DynamicCompartment, self).__init__(dynamic_model, None, wc_lang_compartment) self.id = wc_lang_compartment.id self.biological_type = wc_lang_compartment.biological_type self.physical_type = wc_lang_compartment.physical_type self.species_ids = species_ids # obtain initial compartment volume by sampling its specified distribution if wc_lang_compartment.init_volume and \ are_terms_equivalent(wc_lang_compartment.init_volume.distribution, onto['WC:normal_distribution']): mean = wc_lang_compartment.init_volume.mean std = wc_lang_compartment.init_volume.std if numpy.isnan(std): std = mean / self.MEAN_TO_STD_DEV_RATIO self.init_volume = max(0., random_state.normal(mean, std)) else: raise MultialgorithmError( 'Initial volume must be normally distributed') if math.isnan(self.init_volume): # pragma no cover: cannot be True raise MultialgorithmError( "DynamicCompartment {}: init_volume is NaN, but must be a positive " "number.".format(self.id)) if self.init_volume <= 0: raise MultialgorithmError( "DynamicCompartment {}: init_volume ({}) must be a positive " "number.".format(self.id, self.init_volume)) if not self._is_abstract(): init_density = wc_lang_compartment.init_density.value if math.isnan(init_density): raise MultialgorithmError( f"DynamicCompartment {self.id}: init_density is NaN, but must " f"be a positive number.") if init_density <= 0: raise MultialgorithmError( f"DynamicCompartment {self.id}: init_density ({init_density}) " f"must be a positive number.") self.init_density = init_density
def transform_model_for_dsa_simulation(model): # change the framework of the SSA submodel to experimental deterministic simulation algorithm for submodel in model.submodels: if are_terms_equivalent(submodel.framework, onto['WC:stochastic_simulation_algorithm']): submodel.framework = onto['WC:deterministic_simulation_algorithm'] # to make deterministic initial conditions, set variances of distributions to 0 for conc in model.distribution_init_concentrations: conc.std = 0. for compartment in model.compartments: compartment.init_volume.std = 0.
def _is_abstract(self): """ Indicate whether this is an abstract compartment An abstract compartment has a `physical_type` of `abstract_compartment` as defined in the WC ontology. Its contents do not represent physical matter, so no relationship exists among its mass, volume and density. Its volume is constant and its density is ignored and need not be defined. Abstract compartments are useful for modeling dynamics that are not based on physical chemistry, and for testing models and software. These :obj:`DynamicCompartment` attributes are not initialized in abstract compartments: `init_density`, `init_accounted_density` and `accounted_fraction`. Returns: :obj:`bool`: whether this is an abstract compartment """ return are_terms_equivalent(self.physical_type, onto['WC:abstract_compartment'])
def run(self, model): """ Transform model Args: model (:obj:`Model`): model Returns: :obj:`Model`: same model, but transformed """ config = wc_lang.config.core.get_config()['wc_lang'] flux_min_bound_reversible = config['dfba']['flux_bounds'][ 'min_reversible'] flux_min_bound_irreversible = config['dfba']['flux_bounds'][ 'min_irreversible'] flux_max_bound = config['dfba']['flux_bounds']['max'] for submodel in model.submodels: if are_terms_equivalent(submodel.framework, onto['WC:dynamic_flux_balance_analysis']): for rxn in submodel.reactions: if rxn.reversible: flux_min = flux_min_bound_reversible else: flux_min = flux_min_bound_irreversible flux_max = flux_max_bound if rxn.flux_bounds is None: rxn.flux_bounds = wc_lang.core.FluxBounds() if rxn.flux_bounds.min is None or isnan( rxn.flux_bounds.min): rxn.flux_bounds.min = flux_min else: rxn.flux_bounds.min = max(rxn.flux_bounds.min, flux_min) if rxn.flux_bounds.max is None or isnan( rxn.flux_bounds.max): rxn.flux_bounds.max = flux_max else: rxn.flux_bounds.max = min(rxn.flux_bounds.max, flux_max) rxn.flux_bounds.units = unit_registry.parse_units('M s^-1') return model
def create_dynamic_submodels(self): """ Create dynamic submodels that access shared species Returns: :obj:`list`: list of the simulation's `DynamicSubmodel`\ s Raises: :obj:`MultialgorithmError`: if a submodel cannot be created """ def get_options(self, submodel_class_name): if self.options is not None and submodel_class_name in self.options: return self.options[submodel_class_name] else: return {} # make the simulation's submodels simulation_submodels = {} for lang_submodel in self.model.get_submodels(): if lang_submodel.id in self.skipped_submodels(): continue # don't create a submodel with no reactions if not lang_submodel.reactions: warnings.warn( f"not creating submodel '{lang_submodel.id}': no reactions provided", MultialgorithmWarning) continue if are_terms_equivalent( lang_submodel.framework, onto['WC:stochastic_simulation_algorithm']): simulation_submodel = SsaSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, **get_options(self, 'SsaSubmodel')) elif are_terms_equivalent(lang_submodel.framework, onto['WC:next_reaction_method']): simulation_submodel = NrmSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, **get_options(self, 'NrmSubmodel')) elif are_terms_equivalent( lang_submodel.framework, onto['WC:dynamic_flux_balance_analysis']): # TODO(Arthur): make DFBA submodels work simulation_submodel = DfbaSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, self.wc_sim_config.dfba_time_step, **get_options(self, 'DfbaSubmodel')) elif are_terms_equivalent( lang_submodel.framework, onto['WC:ordinary_differential_equations']): simulation_submodel = OdeSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, self.wc_sim_config.ode_time_step, **get_options(self, 'OdeSubmodel')) elif are_terms_equivalent( lang_submodel.framework, onto['WC:deterministic_simulation_algorithm']): # a deterministic simulation algorithm, used for testing simulation_submodel = DsaSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, **get_options(self, 'DsaSubmodel')) else: raise MultialgorithmError( f"Unsupported lang_submodel framework '{lang_submodel.framework}'" ) simulation_submodels[simulation_submodel.id] = simulation_submodel # add the submodel to the simulation self.simulation.add_object(simulation_submodel) return simulation_submodels
def run(self, model): """ Create implicit exchange reactions for dFBA submodels Args: model (:obj:`Model`): model Returns: :obj:`Model`: same model, but transformed """ config = wc_lang.config.core.get_config()['wc_lang'] create_implicit_exchange_reactions = config['dfba'][ 'create_implicit_exchange_reactions'] if not create_implicit_exchange_reactions: return model ext_comp = model.compartments.get_one( id=config['EXTRACELLULAR_COMPARTMENT_ID']) rxn_id_template = config['dfba']['exchange_reaction_id_template'] rxn_name_template = config['dfba']['exchange_reaction_name_template'] ex_carbon = config['dfba']['flux_bounds']['ex_carbon'] ex_no_carbon = config['dfba']['flux_bounds']['ex_no_carbon'] carbon_flux_bounds = wc_lang.core.FluxBounds() carbon_flux_bounds.min = -ex_carbon carbon_flux_bounds.max = ex_carbon carbon_flux_bounds.units = unit_registry.parse_units('M s^-1') no_carbon_flux_bounds = wc_lang.core.FluxBounds() no_carbon_flux_bounds.min = -ex_no_carbon no_carbon_flux_bounds.max = ex_no_carbon no_carbon_flux_bounds.units = unit_registry.parse_units('M s^-1') for submodel in model.submodels: if are_terms_equivalent(submodel.framework, onto['WC:dynamic_flux_balance_analysis']): for species in submodel.get_children( kind='submodel', __type=wc_lang.core.Species): if species.compartment == ext_comp: id = rxn_id_template.format(submodel.id, species.species_type.id, species.compartment.id) name = rxn_name_template.format( submodel.name or submodel.id, species.species_type.name or species.species_type.id, species.compartment.name or species.compartment.id) participants = [ species.species_coefficients.get_or_create( coefficient=1.) ] reversible = True if species.species_type.structure and species.species_type.structure.has_carbon( ): flux_bounds = carbon_flux_bounds else: flux_bounds = no_carbon_flux_bounds rxn = model.reactions.get_one(id=id) if rxn: assert rxn.submodel == submodel assert rxn.name == name assert rxn.participants == participants assert rxn.reversible == reversible assert wc_lang.core.FluxBounds.Meta.attributes[ 'min'].value_equal(rxn.flux_bounds.min, flux_bounds.min) assert wc_lang.core.FluxBounds.Meta.attributes[ 'max'].value_equal(rxn.flux_bounds.max, flux_bounds.max) assert wc_lang.core.FluxBounds.Meta.attributes[ 'units'].value_equal(rxn.flux_bounds.units, flux_bounds.units) else: rxn = model.reactions.create(id=id) rxn.submodel = submodel rxn.name = name rxn.participants = participants rxn.reversible = reversible rxn.flux_bounds = flux_bounds return model
def run(self, model): """ Split reversible reactions in submodels into separate forward and backward reactions Args: model (:obj:`Model`): model definition Returns: :obj:`Model`: same model definition, but with reversible reactions split into separate forward and backward reactions, their flux bounds adjusted accordingly, and the dFBA objective expression adjusted accordingly """ for submodel in model.submodels: # skip submodels which use an excluded framework if self.excluded_frameworks is not None: if any([ are_terms_equivalent(submodel.framework, excluded_framework) for excluded_framework in self.excluded_frameworks ]): continue for rxn in list(submodel.reactions): if rxn.reversible: # remove reversible reaction model.reactions.remove(rxn) submodel.reactions.remove(rxn) # create separate forward and reverse reactions rxn_for = submodel.reactions.create( model=model, id='{}_forward'.format(rxn.id), name='{} (forward)'.format(rxn.name), reversible=False, evidence=rxn.evidence, conclusions=rxn.conclusions, identifiers=rxn.identifiers, comments=rxn.comments, references=rxn.references, ) rxn_bck = submodel.reactions.create( model=model, id='{}_backward'.format(rxn.id), name='{} (backward)'.format(rxn.name), reversible=False, evidence=rxn.evidence, conclusions=rxn.conclusions, identifiers=rxn.identifiers, comments=rxn.comments, references=rxn.references, ) rxn.evidence = [] rxn.conclusions = [] rxn.identifiers = [] rxn.references = [] # copy participants and negate for backward reaction for part in rxn.participants: rxn_for.participants.append(part) part_back = part.species.species_coefficients.get_one( coefficient=-1 * part.coefficient) if part_back: rxn_bck.participants.append(part_back) else: rxn_bck.participants.create(species=part.species, coefficient=-1 * part.coefficient) rxn.participants = [] # copy rate laws law_for = rxn.rate_laws.get_one( direction=RateLawDirection.forward) law_bck = rxn.rate_laws.get_one( direction=RateLawDirection.backward) if law_for: law_for.reaction = rxn_for law_for.direction = RateLawDirection.forward law_for.id = law_for.gen_id() if law_bck: law_bck.reaction = rxn_bck law_bck.direction = RateLawDirection.forward law_bck.id = law_bck.gen_id() # copy flux bounds if are_terms_equivalent( submodel.framework, onto['WC:dynamic_flux_balance_analysis']): if rxn.flux_bounds: if not math.isnan( rxn.flux_bounds.min) and not math.isnan( rxn.flux_bounds.max): # assume flux_bounds.min <= flux_bounds.max assert rxn.flux_bounds.min <= rxn.flux_bounds.max, \ f"min flux bound greater than max in {rxn.id}" # Mapping of flux bounds to backward and forward reactions # Principles: # lower bounds must be set, and cannot be negative because that would imply reversible # upper bounds may be NaN if not previously set # the forward reaction uses existing bounds that are positive # the backward reaction uses existing bounds that are negative, # swapping min and max and negating signs # NaNs # backward rxn forward rxn # ------------ ----------- # min max min max min max # ----- ----- ---- ---- ---- ---- # NaN NaN 0 NaN 0 NaN # Values # backward rxn forward rxn # ------------ ----------- # min/max min max min max # --------------- ---- ---- ---- ---- # min <= max <= 0 -max -min 0 0 # min <= 0 <= max 0 -min 0 max # 0 <= min <= max 0 0 min max backward_min = 0. backward_max = float('NaN') forward_min = 0. forward_max = float('NaN') if not math.isnan(rxn.flux_bounds.min): if rxn.flux_bounds.min < 0: backward_max = -rxn.flux_bounds.min else: backward_max = 0. forward_min = rxn.flux_bounds.min if not math.isnan(rxn.flux_bounds.max): if 0 < rxn.flux_bounds.max: forward_max = rxn.flux_bounds.max else: forward_max = 0. backward_min = -rxn.flux_bounds.max rxn_bck.flux_bounds = FluxBounds( min=backward_min, max=backward_max, units=rxn.flux_bounds.units) rxn_for.flux_bounds = FluxBounds( min=forward_min, max=forward_max, units=rxn.flux_bounds.units) # transform dFBA objective expression # each dFBA objective expression is transformed for each reaction it uses if rxn.dfba_obj_expression: dfba_obj_expr = rxn.dfba_obj_expression parsed_expr = dfba_obj_expr._parsed_expression # create a new dFBA objective expression # 1. use parsed_expr._obj_tables_tokens to recreate the expression and # the objects it uses, while substituting the split reactions for the reversible reaction new_obj_expr_elements = [] all_reactions = {Reaction: {}, DfbaObjReaction: {}} for ot_token in parsed_expr._obj_tables_tokens: if (ot_token.code == ObjTablesTokenCodes.obj_id and issubclass(ot_token.model_type, Reaction) and ot_token.model_id == rxn.id): new_obj_expr_elements.append( f'({rxn_for.id} - {rxn_bck.id})') all_reactions[Reaction][rxn_for.id] = rxn_for all_reactions[Reaction][rxn_bck.id] = rxn_bck continue if (ot_token.code == ObjTablesTokenCodes.obj_id and issubclass(ot_token.model_type, (Reaction, DfbaObjReaction))): new_obj_expr_elements.append( ot_token.token_string) all_reactions[ot_token.model_type][ ot_token.model_id] = ot_token.model continue new_obj_expr_elements.append(ot_token.token_string) new_obj_expr = ' '.join(new_obj_expr_elements) # 2. create a new DfbaObjectiveExpression dfba_obj_expr, error = DfbaObjectiveExpression.deserialize( new_obj_expr, all_reactions) assert error is None, str(error) rxn.dfba_obj_expression = None rxn_for.dfba_obj_expression = dfba_obj_expr rxn_bck.dfba_obj_expression = dfba_obj_expr submodel.dfba_obj.expression = dfba_obj_expr return model
def run(self, model): """ Split reversible reactions in non-dFBA submodels into separate forward and backward reactions Args: model (:obj:`Model`): model definition Returns: :obj:`Model`: same model definition, but with reversible reactions split into separate forward and backward reactions """ for submodel in model.submodels: if not are_terms_equivalent( submodel.framework, onto['WC:dynamic_flux_balance_analysis']): for rxn in list(submodel.reactions): if rxn.reversible: # remove reversible reaction model.reactions.remove(rxn) submodel.reactions.remove(rxn) # create separate forward and reverse reactions rxn_for = submodel.reactions.create( model=model, id='{}_forward'.format(rxn.id), name='{} (forward)'.format(rxn.name), reversible=False, evidence=rxn.evidence, conclusions=rxn.conclusions, identifiers=rxn.identifiers, comments=rxn.comments, references=rxn.references, ) rxn_bck = submodel.reactions.create( model=model, id='{}_backward'.format(rxn.id), name='{} (backward)'.format(rxn.name), reversible=False, evidence=rxn.evidence, conclusions=rxn.conclusions, identifiers=rxn.identifiers, comments=rxn.comments, references=rxn.references, ) rxn.evidence = [] rxn.conclusions = [] rxn.identifiers = [] rxn.references = [] # copy participants and negate for backward reaction for part in rxn.participants: rxn_for.participants.append(part) part_back = part.species.species_coefficients.get_one( coefficient=-1 * part.coefficient) if part_back: rxn_bck.participants.append(part_back) else: rxn_bck.participants.create( species=part.species, coefficient=-1 * part.coefficient) rxn.participants = [] # copy rate laws law_for = rxn.rate_laws.get_one( direction=RateLawDirection.forward) law_bck = rxn.rate_laws.get_one( direction=RateLawDirection.backward) if law_for: law_for.reaction = rxn_for law_for.direction = RateLawDirection.forward law_for.id = law_for.gen_id() if law_bck: law_bck.reaction = rxn_bck law_bck.direction = RateLawDirection.forward law_bck.id = law_bck.gen_id() # copy dFBA objective: unreachable because only non-dFBA reactions are split if rxn.dfba_obj_expression: dfba_obj_expr = rxn.dfba_obj_expression # pragma: no cover parsed_expr = dfba_obj_expr._parsed_expression # pragma: no cover dfba_obj_expr.expression = parsed_expr.expression = re.sub( r'\b' + rxn.id + r'\b', '({} - {})'.format(rxn_for.id, rxn_bck.id), dfba_obj_expr.expression) # pragma: no cover parsed_expr._objs[Reaction].pop( rxn.id) # pragma: no cover parsed_expr._objs[Reaction][ rxn_for.id] = rxn_for # pragma: no cover parsed_expr._objs[Reaction][ rxn_bck.id] = rxn_bck # pragma: no cover parsed_expr.tokenize() # pragma: no cover rxn.dfba_obj_expression = None # pragma: no cover rxn_for.dfba_obj_expression = dfba_obj_expr # pragma: no cover rxn_bck.dfba_obj_expression = dfba_obj_expr # pragma: no cover return model