Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
    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.
Exemplo n.º 4
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
Exemplo n.º 6
0
    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
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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