Ejemplo n.º 1
0
 def objective(self, value):
     if isinstance(value, sympy.Basic):
         value = self.problem.Objective(value, sloppy=False)
     if not isinstance(value, (dict, optlang.interface.Objective)):
         try:
             reactions = self.reactions.get_by_any(value)
         except KeyError:
             raise ValueError('invalid objective')
         value = {rxn: 1 for rxn in reactions}
     set_objective(self, value, additive=False)
Ejemplo n.º 2
0
 def objective(self, value):
     if isinstance(value, Basic):
         value = self.problem.Objective(value, sloppy=False)
     if not isinstance(value, (dict, optlang.interface.Objective)):
         try:
             reactions = self.reactions.get_by_any(value)
         except KeyError:
             raise ValueError('invalid objective')
         value = {rxn: 1 for rxn in reactions}
     set_objective(self, value, additive=False)
Ejemplo n.º 3
0
 def test_fix_objective_as_constraint_minimize(self, solver, model):
     model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1)
     minimize_glucose = model.problem.Objective(
         model.reactions.EX_glc__D_e.flux_expression, direction='min')
     su.set_objective(model, minimize_glucose)
     su.fix_objective_as_constraint(model)
     fx_name = 'Fixed_objective_{}'.format(model.objective.name)
     constr = model.constraints
     assert (constr[fx_name].lb,
             constr[fx_name].ub) == (None, model.solver.objective.value)
Ejemplo n.º 4
0
 def objective(self, objective):
     if isinstance(objective, str):
         self.model.objective = objective
     elif isinstance(objective, dict):
         from cobra.util.solver import set_objective
         linear_coef = {self.model.reactions.get_by_id(r_id): v for r_id, v in objective.items()}
         set_objective(self.model, linear_coef)
     else:
         raise ValueError(
             'The objective must be a reaction identifier or a dictionary of \
             reaction identifier with respective coeficients.')
Ejemplo n.º 5
0
def test_fix_objective_as_constraint_minimize(model, solver):
    model.solver = solver
    model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1)
    minimize_glucose = model.problem.Objective(
        model.reactions.EX_glc__D_e.flux_expression, direction='min')
    su.set_objective(model, minimize_glucose)
    su.fix_objective_as_constraint(model)
    fx_name = 'fixed_objective_{}'.format(model.objective.name)
    constr = model.constraints
    # Ensure that a solution exists on non-GLPK solvers.
    model.slim_optimize()
    assert (constr[fx_name].lb,
            constr[fx_name].ub) == (None, model.solver.objective.value)
Ejemplo n.º 6
0
def optimize_for_species(model_file_name, species_id, medium,
                         time_point_folder):
    """ Knock out the other species from a two species model, optimize, and save results.
    """

    LOGGER.info('Loading model {0} to optimize for {1}'.format(
        model_file_name, species_id))
    pair_model = load_model_from_file(model_file_name)

    # Figure out the species to knock out in the pair community model.
    species_index = -1
    for index in range(len(pair_model.notes['species'])):
        if pair_model.notes['species'][index]['id'] == species_id:
            species_index = index
    if species_index < 0:
        raise Exception(
            'Species {0} is not a member of the community'.format(species_id))
    if species_index == 0:
        knockout_index = 1
    else:
        knockout_index = 0
    knockout_id = pair_model.notes['species'][knockout_index]['id']
    LOGGER.info('Going to knock out {0} from index {1}'.format(
        knockout_id, knockout_index))

    with pair_model:
        # Apply the medium.
        apply_medium(pair_model, medium)

        # Knock out all of the reactions for the specified species.
        knockout_reactions = pair_model.reactions.query(
            lambda r: r.startswith(knockout_id), 'id')
        for reaction in knockout_reactions:
            reaction.knock_out()

        # Remove the species objective from the community model objective.
        knockout_objective = pair_model.reactions.get_by_id(
            pair_model.notes['species'][knockout_index]['objective'])
        linear_coefficients = linear_reaction_coefficients(pair_model)
        del linear_coefficients[knockout_objective]
        set_objective(pair_model, linear_coefficients)
        save_model_to_file(pair_model,
                           join(time_point_folder, pair_model.id + '.json'))

        # Optimize the community model with the specified species knocked out.
        solution = pair_model.optimize()
        solution.fluxes.to_json(
            join(time_point_folder, pair_model.id +
                 '-solution.json'))  # , orient='records', lines=True

    return solution
Ejemplo n.º 7
0
 def test_fix_objective_as_constraint_minimize(self, model, solver):
     model.solver = solver
     model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1)
     minimize_glucose = model.problem.Objective(
         model.reactions.EX_glc__D_e.flux_expression,
         direction='min')
     su.set_objective(model, minimize_glucose)
     su.fix_objective_as_constraint(model)
     fx_name = 'fixed_objective_{}'.format(model.objective.name)
     constr = model.constraints
     # Ensure that a solution exists on non-GLPK solvers.
     model.slim_optimize()
     assert (constr[fx_name].lb, constr[fx_name].ub) == (
         None, model.solver.objective.value)
Ejemplo n.º 8
0
def test_fix_objective_as_constraint_minimize(model: "Model",
                                              solver: str) -> None:
    """Test fixing present objective as a constraint but as a minimization."""
    model.solver = solver
    model.reactions.Biomass_Ecoli_core.bounds = (0.1, 0.1)
    minimize_glucose = model.problem.Objective(
        model.reactions.EX_glc__D_e.flux_expression, direction="min")
    su.set_objective(model, minimize_glucose)
    su.fix_objective_as_constraint(model)
    fx_name = f"fixed_objective_{model.objective.name}"
    constr = model.constraints
    # Ensure that a solution exists on non-GLPK solvers.
    model.slim_optimize()
    assert (constr[fx_name].lb, constr[fx_name].ub) == (
        None,
        model.solver.objective.value,
    )
Ejemplo n.º 9
0
def model_from_dict(obj):
    """Build a model from a dict.

    Models stored in json are first formulated as a dict that can be read to
    cobra model using this function.

    Parameters
    ----------
    obj : dict
        A dictionary with elements, 'genes', 'compartments', 'id',
        'metabolites', 'notes' and 'reqctions' where 'metabolites', 'genes'
        and 'metabolites' are in turn lists with dictionaries holding all
        attributes to form the corresponding object.

    Returns
    -------
    cora.core.Model
        The generated model.

    See Also
    --------
    cobra.io.json.model_to_dict
    """
    if 'reactions' not in obj:
        raise Exception('JSON object has no reactions attribute. Cannot load.')
    model = Model()
    model.add_metabolites([
        metabolite_from_dict(metabolite) for metabolite in obj['metabolites']
    ])
    model.genes.extend([gene_from_dict(gene) for gene in obj['genes']])
    model.add_reactions(
        [reaction_from_dict(reaction, model) for reaction in obj['reactions']])
    objective_reactions = [
        rxn for rxn in obj['reactions']
        if rxn.get('objective_coefficient', 0) != 0
    ]
    coefficients = {
        model.reactions.get_by_id(rxn['id']): rxn['objective_coefficient']
        for rxn in objective_reactions
    }
    set_objective(model, coefficients)
    for k, v in iteritems(obj):
        if k in {'id', 'name', 'notes', 'compartments', 'annotation'}:
            setattr(model, k, v)
    return model
Ejemplo n.º 10
0
def fix_reaction_to_min(model: cobra.Model,
                        reac: cobra.Reaction) -> Tuple[str, float]:
    """Fix a reaction to its minimum."""
    rev_id = _apply_rev(reac.id, model)
    rev_reac = model.reactions.get_by_id(rev_id) if rev_id else None
    with model:
        if rev_reac is not None:
            prev_bounds = reac.bounds
            set_objective(model, {rev_reac: 1})
            reac.bounds = 0, 0
            lb = model.slim_optimize()
            reac.bounds = prev_bounds
            reac_to_fix = rev_id
        else:
            set_objective(model, {reac: -1})
            lb = model.slim_optimize()
            reac_to_fix = reac.id
    return reac_to_fix, lb
Ejemplo n.º 11
0
def convert_to_irreversible(model):
    """Split reversible reactions into two irreversible reactions

    These two reactions will proceed in opposite directions. This
    guarentees that all reactions in the model will only allow
    positive flux values, which is useful for some modeling problems.

    Arguments
    ----------
    * model: cobra.Model ~ A Model object which will be modified in place.

    """
    #warn("deprecated, not applicable for optlang solvers", DeprecationWarning)
    reactions_to_add = []
    coefficients = {}
    for reaction in model.reactions:
        # If a reaction is reverse only, the forward reaction (which
        # will be constrained to 0) will be left in the model.
        if reaction.lower_bound < 0 and reaction.upper_bound > 0:
            reverse_reaction = Reaction(reaction.id + "_reverse")
            reverse_reaction.lower_bound = max(0, -reaction.upper_bound)
            reverse_reaction.upper_bound = -reaction.lower_bound
            coefficients[
                reverse_reaction] = reaction.objective_coefficient * -1
            reaction.lower_bound = max(0, reaction.lower_bound)
            reaction.upper_bound = max(0, reaction.upper_bound)
            # Make the directions aware of each other
            reaction.notes["reflection"] = reverse_reaction.id
            reverse_reaction.notes["reflection"] = reaction.id
            reaction_dict = {
                k: v * -1
                for k, v in reaction._metabolites.items()
            }
            reverse_reaction.add_metabolites(reaction_dict)
            reverse_reaction._model = reaction._model
            reverse_reaction._genes = reaction._genes
            for gene in reaction._genes:
                gene._reaction.add(reverse_reaction)
            reverse_reaction.subsystem = reaction.subsystem
            reverse_reaction._gene_reaction_rule = reaction._gene_reaction_rule
            reactions_to_add.append(reverse_reaction)
    model.add_reactions(reactions_to_add)
    set_objective(model, coefficients, additive=True)
Ejemplo n.º 12
0
def model_from_dict(obj):
    """Build a model from a dict.

    Models stored in json are first formulated as a dict that can be read to
    cobra model using this function.

    Parameters
    ----------
    obj : dict
        A dictionary with elements, 'genes', 'compartments', 'id',
        'metabolites', 'notes' and 'reactions'; where 'metabolites', 'genes'
        and 'metabolites' are in turn lists with dictionaries holding all
        attributes to form the corresponding object.

    Returns
    -------
    cora.core.Model
        The generated model.

    See Also
    --------
    cobra.io.model_to_dict
    """
    if 'reactions' not in obj:
        raise ValueError('Object has no reactions attribute. Cannot load.')
    model = Model()
    model.add_metabolites(
        [metabolite_from_dict(metabolite) for metabolite in obj['metabolites']]
    )
    model.genes.extend([gene_from_dict(gene) for gene in obj['genes']])
    model.add_reactions(
        [reaction_from_dict(reaction, model) for reaction in obj['reactions']]
    )
    objective_reactions = [rxn for rxn in obj['reactions'] if
                           rxn.get('objective_coefficient', 0) != 0]
    coefficients = {
        model.reactions.get_by_id(rxn['id']): rxn['objective_coefficient'] for
        rxn in objective_reactions}
    set_objective(model, coefficients)
    for k, v in iteritems(obj):
        if k in {'id', 'name', 'notes', 'compartments', 'annotation'}:
            setattr(model, k, v)
    return model
Ejemplo n.º 13
0
    def test_change_objective(self, model):
        expression = 1.0 * model.variables['ENO'] + \
                     1.0 * model.variables['PFK']
        model.objective = model.problem.Objective(
            expression)
        assert model.objective.expression == expression
        model.objective = "ENO"
        eno_obj = model.problem.Objective(
            model.reactions.ENO.flux_expression, direction="max")
        pfk_obj = model.problem.Objective(
            model.reactions.PFK.flux_expression, direction="max")
        assert model.objective == eno_obj

        with model:
            model.objective = "PFK"
            assert model.objective == pfk_obj
        assert model.objective == eno_obj
        expression = model.objective.expression
        atpm = model.reactions.get_by_id("ATPM")
        biomass = model.reactions.get_by_id("Biomass_Ecoli_core")
        with model:
            model.objective = atpm
        assert model.objective.expression == expression
        with model:
            atpm.objective_coefficient = 1
            biomass.objective_coefficient = 2
        assert model.objective.expression == expression

        with model:
            set_objective(model, model.problem.Objective(
                atpm.flux_expression))
            assert model.objective.expression == atpm.flux_expression
        assert model.objective.expression == expression

        expression = model.objective.expression
        with model:
            with model:  # Test to make sure nested contexts are OK
                set_objective(model, atpm.flux_expression,
                              additive=True)
                assert (model.objective.expression ==
                        expression + atpm.flux_expression)
        assert model.objective.expression == expression
Ejemplo n.º 14
0
    def test_change_objective(self, model):
        expression = 1.0 * model.variables['ENO'] + \
                     1.0 * model.variables['PFK']
        model.objective = model.problem.Objective(
            expression)
        assert same_ex(model.objective.expression, expression)
        model.objective = "ENO"
        eno_obj = model.problem.Objective(
            model.reactions.ENO.flux_expression, direction="max")
        pfk_obj = model.problem.Objective(
            model.reactions.PFK.flux_expression, direction="max")
        assert same_ex(model.objective.expression, eno_obj.expression)

        with model:
            model.objective = "PFK"
            assert same_ex(model.objective.expression, pfk_obj.expression)
        assert same_ex(model.objective.expression, eno_obj.expression)
        expression = model.objective.expression
        atpm = model.reactions.get_by_id("ATPM")
        biomass = model.reactions.get_by_id("Biomass_Ecoli_core")
        with model:
            model.objective = atpm
        assert same_ex(model.objective.expression, expression)
        with model:
            atpm.objective_coefficient = 1
            biomass.objective_coefficient = 2
        assert same_ex(model.objective.expression, expression)

        with model:
            set_objective(model, model.problem.Objective(
                atpm.flux_expression))
            assert same_ex(model.objective.expression, atpm.flux_expression)
        assert same_ex(model.objective.expression, expression)

        expression = model.objective.expression
        with model:
            with model:  # Test to make sure nested contexts are OK
                set_objective(model, atpm.flux_expression,
                              additive=True)
                assert same_ex(model.objective.expression,
                               expression + atpm.flux_expression)
        assert same_ex(model.objective.expression, expression)
Ejemplo n.º 15
0
def convert_to_irreversible(cobra_model):
    """Split reversible reactions into two irreversible reactions

    These two reactions will proceed in opposite directions. This
    guarentees that all reactions in the model will only allow
    positive flux values, which is useful for some modeling problems.

    cobra_model: A Model object which will be modified in place.

    """
    warn("deprecated, not applicable for optlang solvers", DeprecationWarning)
    reactions_to_add = []
    coefficients = {}
    for reaction in cobra_model.reactions:
        # If a reaction is reverse only, the forward reaction (which
        # will be constrained to 0) will be left in the model.
        if reaction.lower_bound < 0:
            reverse_reaction = Reaction(reaction.id + "_reverse")
            reverse_reaction.lower_bound = max(0, -reaction.upper_bound)
            reverse_reaction.upper_bound = -reaction.lower_bound
            coefficients[
                reverse_reaction] = reaction.objective_coefficient * -1
            reaction.lower_bound = max(0, reaction.lower_bound)
            reaction.upper_bound = max(0, reaction.upper_bound)
            # Make the directions aware of each other
            reaction.notes["reflection"] = reverse_reaction.id
            reverse_reaction.notes["reflection"] = reaction.id
            reaction_dict = {k: v * -1
                             for k, v in iteritems(reaction._metabolites)}
            reverse_reaction.add_metabolites(reaction_dict)
            reverse_reaction._model = reaction._model
            reverse_reaction._genes = reaction._genes
            for gene in reaction._genes:
                gene._reaction.add(reverse_reaction)
            reverse_reaction.subsystem = reaction.subsystem
            reverse_reaction._gene_reaction_rule = reaction._gene_reaction_rule
            reactions_to_add.append(reverse_reaction)
    cobra_model.add_reactions(reactions_to_add)
    set_objective(cobra_model, coefficients, additive=True)
Ejemplo n.º 16
0
def _from_dict(obj):
    """build a model from a dict"""
    if 'reactions' not in obj:
        raise Exception('JSON object has no reactions attribute. Cannot load.')
    model = Model()
    model.add_metabolites([
        metabolite_from_dict(metabolite) for metabolite in obj['metabolites']
    ])
    model.genes.extend([gene_from_dict(gene) for gene in obj['genes']])
    model.add_reactions(
        [reaction_from_dict(reaction, model) for reaction in obj['reactions']])
    objective_reactions = [
        rxn for rxn in obj['reactions']
        if rxn.get('objective_coefficient', 0) != 0
    ]
    coefficients = {
        model.reactions.get_by_id(rxn['id']): rxn['objective_coefficient']
        for rxn in objective_reactions
    }
    set_objective(model, coefficients)
    for k, v in iteritems(obj):
        if k in {'id', 'name', 'notes', 'compartments', 'annotation'}:
            setattr(model, k, v)
    return model
Ejemplo n.º 17
0
def _fva_step(reaction_id):
    """Run a maximization and a minization.

    Modified from cobrapy to account for possibly duplicated (`_REV`) reactions
    in the EC model.
    """
    global _model
    global _target
    target = _model.__getattribute__(_target)
    reac = target.get_by_id(reaction_id)
    rev_id = _apply_rev(reaction_id, _model)
    rev_reac = target.get_by_id(rev_id) if rev_id else None
    set_objective(_model, {reac: 1})
    if rev_reac is not None:
        # if reac with duplicate is optimized, block counterpart
        prev_bounds = rev_reac.bounds
        rev_reac.bounds = 0, 0
        ub = _model.slim_optimize()
        rev_reac.bounds = prev_bounds
        # and use the reverse for the minimum (blocking forward)
        prev_bounds = reac.bounds
        set_objective(_model, {rev_reac: 1}, False)
        reac.bounds = 0, 0
        lb = -_model.slim_optimize()
        reac.bounds = prev_bounds
    else:
        ub = _model.slim_optimize()
        set_objective(_model, {reac: -1}, False)
        lb = _model.slim_optimize()
    # handle infeasible case
    if isnan_none(lb) or isnan_none(ub):
        lb = 0.0 if isnan_none(lb) else lb
        ub = 0.0 if isnan_none(ub) else ub
        LOGGER.warning(
            "Could not get flux for reaction %s,  setting "
            "it to 0. This is usually due to numerical instability.",
            reaction_id,
        )
    return reaction_id, lb, ub
Ejemplo n.º 18
0
def single_species_knockout(community, species_id):
    """ Knockout a species from a community model.

    Parameters
    ----------
    community : cobra.core.Model
        Community model
    species_id : str
        ID of species to knockout from community model

    Returns
    -------
    cobra.Solution
        Solution after optimizing model with species knocked out

    Raises
    ------
    Exception
        If specified species is not a member of the community.
    """

    # Make sure the species is in the community model.
    species_index = -1
    for index in range(len(community.notes['species'])):
        if community.notes['species'][index]['id'] == species_id:
            species_index = index
    if species_index < 0:
        raise Exception(
            'Species {0} is not a member of the community'.format(species_id))

    if cobra06:
        with community:
            # Knock out all of the reactions for the specified species.
            species_reactions = community.reactions.query(
                lambda r: r.startswith(species_id), 'id')
            for reaction in species_reactions:
                reaction.knock_out()

            # Remove the species objective from the community model objective.
            species_objective = community.reactions.get_by_id(
                community.notes['species'][species_index]['objective'])
            linear_coefficients = linear_reaction_coefficients(community)
            del linear_coefficients[species_objective]
            set_objective(community, linear_coefficients)

            # Optimize the community model with the specified species knocked out.
            solution = community.optimize()

    else:
        # Knock out all of the reactions for the specified species.
        species_reactions = community.reactions.query(
            lambda r: r.startswith(species_id), 'id')
        saved_bounds = dict()
        for reaction in species_reactions:
            saved_bounds[reaction.id] = reaction.bounds
            reaction.knock_out()

        # Remove the species objective from the community model objective.
        species_objectives = dict()
        for reaction in community.objective:
            if reaction.id.startswith(species_id):
                species_objectives[reaction] = reaction.objective_coefficient
                reaction.objective_coefficient = 0.

        # Optimize the community model with the specified species knocked out.
        solution = community.optimize()

        # Restore all species reactions to original values.
        for reaction in species_reactions:
            reaction.bounds = saved_bounds[reaction.id]
        for reaction in species_objectives:
            reaction.objective_coefficient = species_objectives[reaction]

    return solution
Ejemplo n.º 19
0
def create_community_model(source_models):
    """ Create a community model from a list of source models.

    A community model contains all of the reactions and metabolites from the
    source models. As a source model for a species is added to the community
    model, a model ID prefix is added to the IDs of reactions and metabolites.
    This is the same as assigning each species to a different compartment,
    which is required because cells are closed compartments that do not share
    metabolites. A shared compartment is added to the community model and all
    unique exchange reactions from the source models are added to the community
    model to exchange metabolites between the shared compartment and the system
    boundary. For each source model, all exchange reactions are converted to
    transport reactions to move the metabolite between the shared compartment
    and the extracellular compartment of the source model.

    Parameters
    ----------
    source_models : list of str
        List of path names to model files

    Returns
    -------
    cobra.core.Model
        Community model

    Raises
    ------
    Exception
        If there are less than two models in the list of source models.
        If there is a duplicate model ID in the list of source models.
        If there is more than one objective
        If the type of IDs in the model metabolites could not be determined.
    """

    # There must be at least two source models to make a community model.
    if len(source_models) < 2:
        raise Exception(
            'There must be at least two species in a community model')

    # Start with an empty model.
    community = Model('Community')
    community.compartments['u'] = 'Lumen'

    # Keep track of the source models. Each element is a tuple with ID and source file name.
    community.notes['species'] = list()

    # IDs of all exchange reactions in the community.
    exchange_reaction_ids = set()

    # IDs of all models in the community (duplicates are not allowed).
    model_ids = set()

    # Add of the source models to the community model.
    for model_filename in source_models:
        # Load the model from a file.
        model = load_model_from_file(model_filename)

        # Since the model ID is used as a prefix for reactions and metabolites it must be unique.
        if model.id in model_ids:
            raise Exception(
                'Model ID {0} from {1} is a duplicate in the community'.format(
                    model.id, model_filename))
        model_ids.add(model.id)

        # Check for multiple objectives in the model (which changed significantly in cobra 0.6).
        if cobra06:
            linear_coefficients = linear_reaction_coefficients(model)
            if len(linear_coefficients) != 1:
                raise Exception(
                    'Wrong number of objectives for model {0}, only one growth objective is allowed'
                    .format(model_filename))
            objective_id = '{0}_{1}'.format(
                model.id,
                six.next(six.iterkeys(linear_coefficients)).id)
            objective_value = six.next(six.itervalues(linear_coefficients))
        else:
            if len(model.objective) != 1:
                raise Exception(
                    'Wrong number of objectives for model {0}, only one growth objective is allowed'
                    .format(model_filename))
            objective_id = '{0}_{1}'.format(
                model.id,
                six.next(six.iterkeys(model.objective)).id)
            objective_value = six.next(six.itervalues(model.objective))

        # All metabolites need to have a compartment suffix.
        for metabolite in model.metabolites:
            metabolite.notes['type'] = _id_type(metabolite.id)
        unknown = model.metabolites.query(lambda x: 'unknown' in x['type'],
                                          'notes')
        if len(unknown) > 0:
            raise Exception(
                'Unknown compartment suffixes found in metabolites for {0}'.
                format(model_filename))

        # Get the exchange reactions from the species model.
        exchange_reactions = model.reactions.query(
            lambda x: x.startswith('EX_'), 'id')

        # Add any exchange reactions that are not already in the community model.
        for reaction in exchange_reactions:
            if reaction.id not in exchange_reaction_ids:
                exchange_reaction_ids.add(reaction.id)
                if cobra06:
                    metabolite = six.next(six.iterkeys(
                        reaction.metabolites)).copy()
                    metabolite.compartment = community_compartment
                    metabolite.id = _change_compartment(
                        metabolite.id, community_compartment,
                        metabolite.notes['type'])
                    rxn = community.add_boundary(
                        metabolite)  # This is slow on cobra06
                    rxn.id = reaction.id  # Keep same ID as species model
                else:
                    community.add_reactions([copy_exchange_reaction(reaction)])

        # Update the reaction IDs with species prefix and convert exchange reactions to transport reactions.
        for reaction in model.reactions:
            # Change the exchange reactions to transport reactions to move the metabolite between the
            # community compartment and the extracellular compartment of this organism.
            if reaction in exchange_reactions:
                e_metabolite = six.next(six.iterkeys(reaction.metabolites))
                u_metabolite = community.metabolites.get_by_id(
                    _change_compartment(e_metabolite.id, community_compartment,
                                        e_metabolite.notes['type']))
                reaction.add_metabolites({u_metabolite: 1.})
                reaction.id = '{0}_TR_{1}'.format(
                    model.id, reaction.id[3:])  # Strip "EX_" prefix
                reaction.bounds = (-1000., 1000.)
            else:
                reaction.id = '{0}_{1}'.format(model.id, reaction.id)

        # Update the metabolite IDs with species prefix.
        for metabolite in model.metabolites:
            if metabolite.compartment != community_compartment:
                metabolite.id = '{0}_{1}'.format(model.id, metabolite.id)

        # Add the species model to the community model.
        community += model
        if cobra06:
            # Workaround until agreement reached on issue #505.
            objective_reaction = community.reactions.get_by_id(objective_id)
            set_objective(community, {objective_reaction: objective_value},
                          additive=True)
        species = {
            'id': model.id,
            'objective': objective_id,
            'filename': model_filename
        }
        community.notes['species'].append(species)

    # Update the community ID to include all of the species in the community.
    # Note that the community ID is used as the file name when saving the
    # community model to a JSON file.
    community.id = 'x'.join(model_ids)
    return community
Ejemplo n.º 20
0
def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
                                      legacy_metabolite=False,
                                      print_time=False, use_hyphens=False):
    """convert an SBML XML file into a cobra.Model object.

    Supports SBML Level 2 Versions 1 and 4.  The function will detect if the
    SBML fbc package is used in the file and run the converter if the fbc
    package is used.

    Parameters
    ----------
    sbml_filename: string
    old_sbml: bool
        Set to True if the XML file has metabolite formula appended to
        metabolite names. This was a poorly designed artifact that persists in
        some models.
    legacy_metabolite: bool
        If True then assume that the metabolite id has the compartment id
         appended after an underscore (e.g. _c for cytosol). This has not been
         implemented but will be soon.
    print_time: bool
         deprecated
    use_hyphens: bool
        If True, double underscores (__) in an SBML ID will be converted to
        hyphens

    Returns
    -------
    Model : The parsed cobra model
    """
    if not libsbml:
        raise ImportError('create_cobra_model_from_sbml_file '
                          'requires python-libsbml')

    __default_lower_bound = -1000
    __default_upper_bound = 1000
    __default_objective_coefficient = 0
    # Ensure that the file exists
    if not isfile(sbml_filename):
        raise IOError('Your SBML file is not found: %s' % sbml_filename)
    # Expressions to change SBML Ids to Palsson Lab Ids
    metabolite_re = re.compile('^M_')
    reaction_re = re.compile('^R_')
    compartment_re = re.compile('^C_')
    if print_time:
        warn("print_time is deprecated", DeprecationWarning)
    model_doc = libsbml.readSBML(sbml_filename)
    if model_doc.getPlugin("fbc") is not None:
        from libsbml import ConversionProperties, LIBSBML_OPERATION_SUCCESS
        conversion_properties = ConversionProperties()
        conversion_properties.addOption(
            "convert fbc to cobra", True, "Convert FBC model to Cobra model")
        result = model_doc.convert(conversion_properties)
        if result != LIBSBML_OPERATION_SUCCESS:
            raise Exception("Conversion of SBML+fbc to COBRA failed")
    sbml_model = model_doc.getModel()
    sbml_model_id = sbml_model.getId()
    sbml_species = sbml_model.getListOfSpecies()
    sbml_reactions = sbml_model.getListOfReactions()
    sbml_compartments = sbml_model.getListOfCompartments()
    compartment_dict = dict([(compartment_re.split(x.getId())[-1], x.getName())
                             for x in sbml_compartments])
    if legacy_metabolite:
        # Deal with the palsson lab appending the compartment id to the
        # metabolite id
        new_dict = {}
        for the_id, the_name in compartment_dict.items():
            if the_name == '':
                new_dict[the_id[0].lower()] = the_id
            else:
                new_dict[the_id] = the_name
        compartment_dict = new_dict
        legacy_compartment_converter = dict(
            [(v, k) for k, v in iteritems(compartment_dict)])

    cobra_model = Model(sbml_model_id)
    metabolites = []
    metabolite_dict = {}
    # Convert sbml_metabolites to cobra.Metabolites
    for sbml_metabolite in sbml_species:
        # Skip sbml boundary species
        if sbml_metabolite.getBoundaryCondition():
            continue

        if (old_sbml or legacy_metabolite) and \
                sbml_metabolite.getId().endswith('_b'):
            # Deal with incorrect sbml from bigg.ucsd.edu
            continue
        tmp_metabolite = Metabolite()
        metabolite_id = tmp_metabolite.id = sbml_metabolite.getId()
        tmp_metabolite.compartment = compartment_re.split(
            sbml_metabolite.getCompartment())[-1]
        if legacy_metabolite:
            if tmp_metabolite.compartment not in compartment_dict:
                tmp_metabolite.compartment = legacy_compartment_converter[
                    tmp_metabolite.compartment]
            tmp_metabolite.id = parse_legacy_id(
                tmp_metabolite.id, tmp_metabolite.compartment,
                use_hyphens=use_hyphens)
        if use_hyphens:
            tmp_metabolite.id = metabolite_re.split(
                tmp_metabolite.id)[-1].replace('__', '-')
        else:
            # Just in case the SBML ids are ill-formed and use -
            tmp_metabolite.id = metabolite_re.split(
                tmp_metabolite.id)[-1].replace('-', '__')
        tmp_metabolite.name = sbml_metabolite.getName()
        tmp_formula = ''
        tmp_metabolite.notes = parse_legacy_sbml_notes(
            sbml_metabolite.getNotesString())
        if sbml_metabolite.isSetCharge():
            tmp_metabolite.charge = sbml_metabolite.getCharge()
        if "CHARGE" in tmp_metabolite.notes:
            note_charge = tmp_metabolite.notes["CHARGE"][0]
            try:
                note_charge = float(note_charge)
                if note_charge == int(note_charge):
                    note_charge = int(note_charge)
            except:
                warn("charge of %s is not a number (%s)" %
                     (tmp_metabolite.id, str(note_charge)))
            else:
                if ((tmp_metabolite.charge is None) or
                        (tmp_metabolite.charge == note_charge)):
                    tmp_metabolite.notes.pop("CHARGE")
                    # set charge to the one from notes if not assigend before
                    # the same
                    tmp_metabolite.charge = note_charge
                else:  # tmp_metabolite.charge != note_charge
                    msg = "different charges specified for %s (%d and %d)"
                    msg = msg % (tmp_metabolite.id,
                                 tmp_metabolite.charge, note_charge)
                    warn(msg)
                    # Chances are a 0 note charge was written by mistake. We
                    # will default to the note_charge in this case.
                    if tmp_metabolite.charge == 0:
                        tmp_metabolite.charge = note_charge

        for the_key in tmp_metabolite.notes.keys():
            if the_key.lower() == 'formula':
                tmp_formula = tmp_metabolite.notes.pop(the_key)[0]
                break
        if tmp_formula == '' and old_sbml:
            tmp_formula = tmp_metabolite.name.split('_')[-1]
            tmp_metabolite.name = tmp_metabolite.name[:-len(tmp_formula) - 1]
        tmp_metabolite.formula = tmp_formula
        metabolite_dict.update({metabolite_id: tmp_metabolite})
        metabolites.append(tmp_metabolite)
    cobra_model.add_metabolites(metabolites)

    # Construct the vectors and matrices for holding connectivity and numerical
    # info to feed to the cobra toolbox.
    # Always assume steady state simulations so b is set to 0
    cobra_reaction_list = []
    coefficients = {}
    for sbml_reaction in sbml_reactions:
        if use_hyphens:
            # Change the ids to match conventions used by the Palsson lab.
            reaction = Reaction(reaction_re.split(
                sbml_reaction.getId())[-1].replace('__', '-'))
        else:
            # Just in case the SBML ids are ill-formed and use -
            reaction = Reaction(reaction_re.split(
                sbml_reaction.getId())[-1].replace('-', '__'))
        cobra_reaction_list.append(reaction)
        # reaction.exchange_reaction = 0
        reaction.name = sbml_reaction.getName()
        cobra_metabolites = {}
        # Use the cobra.Metabolite class here
        for sbml_metabolite in sbml_reaction.getListOfReactants():
            tmp_metabolite_id = sbml_metabolite.getSpecies()
            # This deals with boundary metabolites
            if tmp_metabolite_id in metabolite_dict:
                tmp_metabolite = metabolite_dict[tmp_metabolite_id]
                cobra_metabolites[tmp_metabolite] = - \
                    sbml_metabolite.getStoichiometry()
        for sbml_metabolite in sbml_reaction.getListOfProducts():
            tmp_metabolite_id = sbml_metabolite.getSpecies()
            # This deals with boundary metabolites
            if tmp_metabolite_id in metabolite_dict:
                tmp_metabolite = metabolite_dict[tmp_metabolite_id]
                # Handle the case where the metabolite was specified both
                # as a reactant and as a product.
                if tmp_metabolite in cobra_metabolites:
                    warn("%s appears as a reactant and product %s" %
                         (tmp_metabolite_id, reaction.id))
                    cobra_metabolites[
                        tmp_metabolite] += sbml_metabolite.getStoichiometry()
                    # if the combined stoichiometry is 0, remove the metabolite
                    if cobra_metabolites[tmp_metabolite] == 0:
                        cobra_metabolites.pop(tmp_metabolite)
                else:
                    cobra_metabolites[
                        tmp_metabolite] = sbml_metabolite.getStoichiometry()
        # check for nan
        for met, v in iteritems(cobra_metabolites):
            if isnan(v) or isinf(v):
                warn("invalid value %s for metabolite '%s' in reaction '%s'" %
                     (str(v), met.id, reaction.id))
        reaction.add_metabolites(cobra_metabolites)
        # Parse the kinetic law info here.
        parameter_dict = {}
        # If lower and upper bounds are specified in the Kinetic Law then
        # they override the sbml reversible attribute.  If they are not
        # specified then the bounds are determined by getReversible.
        if not sbml_reaction.getKineticLaw():

            if sbml_reaction.getReversible():
                parameter_dict['lower_bound'] = __default_lower_bound
                parameter_dict['upper_bound'] = __default_upper_bound
            else:
                # Assume that irreversible reactions only proceed from left to
                # right.
                parameter_dict['lower_bound'] = 0
                parameter_dict['upper_bound'] = __default_upper_bound

            parameter_dict[
                'objective_coefficient'] = __default_objective_coefficient
        else:
            for sbml_parameter in \
                    sbml_reaction.getKineticLaw().getListOfParameters():
                parameter_dict[
                    sbml_parameter.getId().lower()] = sbml_parameter.getValue()

        if 'lower_bound' in parameter_dict:
            reaction.lower_bound = parameter_dict['lower_bound']
        elif 'lower bound' in parameter_dict:
            reaction.lower_bound = parameter_dict['lower bound']
        elif sbml_reaction.getReversible():
            reaction.lower_bound = __default_lower_bound
        else:
            reaction.lower_bound = 0

        if 'upper_bound' in parameter_dict:
            reaction.upper_bound = parameter_dict['upper_bound']
        elif 'upper bound' in parameter_dict:
            reaction.upper_bound = parameter_dict['upper bound']
        else:
            reaction.upper_bound = __default_upper_bound

        objective_coefficient = parameter_dict.get(
            'objective_coefficient', parameter_dict.get(
                'objective_coefficient', __default_objective_coefficient))
        if objective_coefficient != 0:
            coefficients[reaction] = objective_coefficient

        # ensure values are not set to nan or inf
        if isnan(reaction.lower_bound) or isinf(reaction.lower_bound):
            reaction.lower_bound = __default_lower_bound
        if isnan(reaction.upper_bound) or isinf(reaction.upper_bound):
            reaction.upper_bound = __default_upper_bound

        reaction_note_dict = parse_legacy_sbml_notes(
            sbml_reaction.getNotesString())
        # Parse the reaction notes.
        # POTENTIAL BUG: DEALING WITH LEGACY 'SBML' THAT IS NOT IN A
        # STANDARD FORMAT
        # TODO: READ IN OTHER NOTES AND GIVE THEM A reaction_ prefix.
        # TODO: Make sure genes get added as objects
        if 'GENE ASSOCIATION' in reaction_note_dict:
            rule = reaction_note_dict['GENE ASSOCIATION'][0]
            try:
                rule.encode('ascii')
            except (UnicodeEncodeError, UnicodeDecodeError):
                warn("gene_reaction_rule '%s' is not ascii compliant" % rule)
            if rule.startswith("&quot;") and rule.endswith("&quot;"):
                rule = rule[6:-6]
            reaction.gene_reaction_rule = rule
            if 'GENE LIST' in reaction_note_dict:
                reaction.systematic_names = reaction_note_dict['GENE LIST'][0]
            elif ('GENES' in reaction_note_dict and
                  reaction_note_dict['GENES'] != ['']):
                reaction.systematic_names = reaction_note_dict['GENES'][0]
            elif 'LOCUS' in reaction_note_dict:
                gene_id_to_object = dict([(x.id, x) for x in reaction._genes])
                for the_row in reaction_note_dict['LOCUS']:
                    tmp_row_dict = {}
                    the_row = 'LOCUS:' + the_row.lstrip('_').rstrip('#')
                    for the_item in the_row.split('#'):
                        k, v = the_item.split(':')
                        tmp_row_dict[k] = v
                    tmp_locus_id = tmp_row_dict['LOCUS']
                    if 'TRANSCRIPT' in tmp_row_dict:
                        tmp_locus_id = tmp_locus_id + \
                                       '.' + tmp_row_dict['TRANSCRIPT']

                    if 'ABBREVIATION' in tmp_row_dict:
                        gene_id_to_object[tmp_locus_id].name = tmp_row_dict[
                            'ABBREVIATION']

        if 'SUBSYSTEM' in reaction_note_dict:
            reaction.subsystem = reaction_note_dict.pop('SUBSYSTEM')[0]

        reaction.notes = reaction_note_dict

    # Now, add all of the reactions to the model.
    cobra_model.id = sbml_model.getId()
    # Populate the compartment list - This will be done based on
    # cobra.Metabolites in cobra.Reactions in the future.
    cobra_model.compartments = compartment_dict

    cobra_model.add_reactions(cobra_reaction_list)
    set_objective(cobra_model, coefficients)
    return cobra_model
Ejemplo n.º 21
0
def from_mat_struct(mat_struct, model_id=None, inf=inf):
    """create a model from the COBRA toolbox struct

    The struct will be a dict read in by scipy.io.loadmat

    """
    m = mat_struct
    if m.dtype.names is None:
        raise ValueError("not a valid mat struct")
    if not {"rxns", "mets", "S", "lb", "ub"} <= set(m.dtype.names):
        raise ValueError("not a valid mat struct")
    if "c" in m.dtype.names:
        c_vec = m["c"][0, 0]
    else:
        c_vec = None
        warn("objective vector 'c' not found")
    model = Model()
    if model_id is not None:
        model.id = model_id
    elif "description" in m.dtype.names:
        description = m["description"][0, 0][0]
        if not isinstance(description, string_types) and len(description) > 1:
            model.id = description[0]
            warn("Several IDs detected, only using the first.")
        else:
            model.id = description
    else:
        model.id = "imported_model"
    for i, name in enumerate(m["mets"][0, 0]):
        new_metabolite = Metabolite()
        new_metabolite.id = str(name[0][0])
        if all(var in m.dtype.names for var in
               ['metComps', 'comps', 'compNames']):
            comp_index = m["metComps"][0, 0][i][0] - 1
            new_metabolite.compartment = m['comps'][0, 0][comp_index][0][0]
            if new_metabolite.compartment not in model.compartments:
                comp_name = m['compNames'][0, 0][comp_index][0][0]
                model.compartments[new_metabolite.compartment] = comp_name
        else:
            new_metabolite.compartment = _get_id_compartment(new_metabolite.id)
            if new_metabolite.compartment not in model.compartments:
                model.compartments[
                    new_metabolite.compartment] = new_metabolite.compartment
        try:
            new_metabolite.name = str(m["metNames"][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_metabolite.formula = str(m["metFormulas"][0][0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_metabolite.charge = float(m["metCharge"][0, 0][i][0])
            int_charge = int(new_metabolite.charge)
            if new_metabolite.charge == int_charge:
                new_metabolite.charge = int_charge
        except (IndexError, ValueError):
            pass
        model.add_metabolites([new_metabolite])
    new_reactions = []
    coefficients = {}
    for i, name in enumerate(m["rxns"][0, 0]):
        new_reaction = Reaction()
        new_reaction.id = str(name[0][0])
        new_reaction.lower_bound = float(m["lb"][0, 0][i][0])
        new_reaction.upper_bound = float(m["ub"][0, 0][i][0])
        if isinf(new_reaction.lower_bound) and new_reaction.lower_bound < 0:
            new_reaction.lower_bound = -inf
        if isinf(new_reaction.upper_bound) and new_reaction.upper_bound > 0:
            new_reaction.upper_bound = inf
        if c_vec is not None:
            coefficients[new_reaction] = float(c_vec[i][0])
        try:
            new_reaction.gene_reaction_rule = str(m['grRules'][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_reaction.name = str(m["rxnNames"][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_reaction.subsystem = str(m['subSystems'][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        new_reactions.append(new_reaction)
    model.add_reactions(new_reactions)
    set_objective(model, coefficients)
    coo = scipy_sparse.coo_matrix(m["S"][0, 0])
    for i, j, v in zip(coo.row, coo.col, coo.data):
        model.reactions[j].add_metabolites({model.metabolites[i]: v})
    return model
Ejemplo n.º 22
0
 def objective_coefficient(self, value):
     if self.model is None:
         raise AttributeError('cannot assign objective to a missing model')
     if self.flux_expression is not None:
         set_objective(self.model, {self: value}, additive=True)
Ejemplo n.º 23
0
def convert_to_irreversible(cobra_model):
    """
    Split reversible reactions into two irreversible reactions: one going in
    the forward direction, the other in the backward direction. In this manner,
    all reactions in the model carry non-negative flux values. Forward reactions
    are tagged as "forward" while backward reactions as "backward".

    cobra_model: A Model object which will be modified in place.
    Modified from the deprecated cobrapy version by Semidan Robaina,
    February 2019.
    """
    reactions_to_add = []
    coefficients = {}

    def onlyBackward(reaction):
        return reaction.lower_bound < 0 and reaction.upper_bound <= 0

    def backwardAndForward(reaction):
        return reaction.lower_bound < 0 and reaction.upper_bound > 0

    def changeDirection(reaction):
        def swapSign(number):
            return -number

        lb = swapSign(reaction.upper_bound)
        ub = swapSign(reaction.lower_bound)
        reaction.lower_bound = lb
        reaction.upper_bound = ub
        reaction.objective_coefficient * -1
        reaction.notes["reflection"] = 'only reverse'
        reaction.id += '_reverse'
        reaction.name += '_reverse'
        for met in reaction._metabolites.keys():
            reaction._metabolites[met] *= -1

    def createBackwardReaction(reaction):
        backward_reaction = cobraReaction(reaction.id + '_reverse')
        backward_reaction.lower_bound = 0
        backward_reaction.upper_bound = -reaction.lower_bound
        reaction_dict = {
            k: v * -1
            for k, v in iteritems(reaction._metabolites)
        }
        backward_reaction.add_metabolites(reaction_dict)
        backward_reaction._model = reaction._model
        backward_reaction._genes = reaction._genes
        for gene in reaction._genes:
            gene._reaction.add(backward_reaction)
        backward_reaction.subsystem = reaction.subsystem
        backward_reaction.name = reaction.name + '_reverse'
        backward_reaction._gene_reaction_rule = reaction._gene_reaction_rule
        coefficients[backward_reaction] = reaction.objective_coefficient * -1
        return backward_reaction

    for reaction in cobra_model.reactions:
        if onlyBackward(reaction):
            changeDirection(reaction)
        elif backwardAndForward(reaction):
            backward_reaction = createBackwardReaction(reaction)
            reactions_to_add.append(backward_reaction)
            reaction.lower_bound = 0

    cobra_model.add_reactions(reactions_to_add)
    set_objective(cobra_model, coefficients, additive=True)
Ejemplo n.º 24
0
def from_mat_struct(mat_struct, model_id=None, inf=inf):
    """create a model from the COBRA toolbox struct

    The struct will be a dict read in by scipy.io.loadmat

    """
    m = mat_struct
    if m.dtype.names is None:
        raise ValueError("not a valid mat struct")
    if not {"rxns", "mets", "S", "lb", "ub"} <= set(m.dtype.names):
        raise ValueError("not a valid mat struct")
    if "c" in m.dtype.names:
        c_vec = m["c"][0, 0]
    else:
        c_vec = None
        warn("objective vector 'c' not found")
    model = Model()
    if model_id is not None:
        model.id = model_id
    elif "description" in m.dtype.names:
        description = m["description"][0, 0][0]
        if not isinstance(description, string_types) and len(description) > 1:
            model.id = description[0]
            warn("Several IDs detected, only using the first.")
        else:
            model.id = description
    else:
        model.id = "imported_model"
    for i, name in enumerate(m["mets"][0, 0]):
        new_metabolite = Metabolite()
        new_metabolite.id = str(name[0][0])
        if all(var in m.dtype.names
               for var in ['metComps', 'comps', 'compNames']):
            comp_index = m["metComps"][0, 0][i][0] - 1
            new_metabolite.compartment = m['comps'][0, 0][comp_index][0][0]
            if new_metabolite.compartment not in model.compartments:
                comp_name = m['compNames'][0, 0][comp_index][0][0]
                model.compartments[new_metabolite.compartment] = comp_name
        else:
            new_metabolite.compartment = _get_id_compartment(new_metabolite.id)
            if new_metabolite.compartment not in model.compartments:
                model.compartments[
                    new_metabolite.compartment] = new_metabolite.compartment
        try:
            new_metabolite.name = str(m["metNames"][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_metabolite.formula = str(m["metFormulas"][0][0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_metabolite.charge = float(m["metCharge"][0, 0][i][0])
            int_charge = int(new_metabolite.charge)
            if new_metabolite.charge == int_charge:
                new_metabolite.charge = int_charge
        except (IndexError, ValueError):
            pass
        model.add_metabolites([new_metabolite])
    new_reactions = []
    coefficients = {}
    for i, name in enumerate(m["rxns"][0, 0]):
        new_reaction = Reaction()
        new_reaction.id = str(name[0][0])
        new_reaction.lower_bound = float(m["lb"][0, 0][i][0])
        new_reaction.upper_bound = float(m["ub"][0, 0][i][0])
        if isinf(new_reaction.lower_bound) and new_reaction.lower_bound < 0:
            new_reaction.lower_bound = -inf
        if isinf(new_reaction.upper_bound) and new_reaction.upper_bound > 0:
            new_reaction.upper_bound = inf
        if c_vec is not None:
            coefficients[new_reaction] = float(c_vec[i][0])
        try:
            new_reaction.gene_reaction_rule = str(m['grRules'][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_reaction.name = str(m["rxnNames"][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        try:
            new_reaction.subsystem = str(m['subSystems'][0, 0][i][0][0])
        except (IndexError, ValueError):
            pass
        new_reactions.append(new_reaction)
    model.add_reactions(new_reactions)
    set_objective(model, coefficients)
    coo = scipy_sparse.coo_matrix(m["S"][0, 0])
    for i, j, v in zip(coo.row, coo.col, coo.data):
        model.reactions[j].add_metabolites({model.metabolites[i]: v})
    return model
Ejemplo n.º 25
0
	def min_incon_parsi(self, **kwargs):
		'''The main step called by compute_met_form to solve the MIP problem.
		Return a min_incon_parsi_info object summarizing the results of solving MIP.
			formulae: result formulae
			infeas: infeasibility of each solve
			bound: bound used for total inconsistency or the relaxation value eps0 for each solve
			obj: objective function value for each solve
			solution: solution values for each type of variables (m, xp, xn, Ap, An)
			met_model: the MIP problems solved for each connected set of elements as individual cobra models
			final: the final status of the solution
			sol_stat: solution status for each connected set of elements
			sol_constrain: 'minIncon' or 'minFill' indicating the solution used to constrain the minimal formulae problem.
		'''
		inf, neg_inf = self.infinity, self.negative_infinity
		pre = self.pre
		metK, metU, metF, rxnK, ele, ele_connect, met_fill_connect, feasTol, digitRounded \
		= pre.met_known, pre.met_unknown, pre.met_fill, pre.rxn_known, \
		pre.ele, pre.ele_connect, pre.met_fill_connect, pre.feasTol, pre.digitRounded
		model = self.model
		kwargs['objective_sense'] = 'minimize' #always minimize
		#data to be stored
		infeas, bound, solution, met_model, sol_constrain, sol_stat, obj = ({} for i in range(7))
		for k in ['minIncon', 'minFill', 'minForm']: #pre-assignment
			solution[k] = {'m': {}, 'xp': {}, 'xn': {}, 'Ap': {}, 'An': {}}

		#Optimize for each connected componenet in ele_connect
		ct = 0
		for eCC in ele_connect:
			ct += 1
			print("Optimizing for %d / %d set of connected elements ... %s" %(ct, len(ele_connect), datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
			metModelJ = Model(', '.join(eCC))
			metModelJ.solver = self.__solver
			infeasJ, boundJ, objJ = {}, {}, {}
			constraint = {e: {j: Metabolite(j.id + ',' + e) for j in rxnK} for e in eCC}
			m = {e: {i: Reaction('m_' + i.id + ',' + e) for i in metU} for e in eCC}
			xp = {e: {j: Reaction('xp_' + j.id + ',' + e) for j in rxnK} for e in eCC}
			xn = {e: {j: Reaction('xn_' + j.id + ',' + e) for j in rxnK} for e in eCC}
			#add adjustment variable if filling mets associated with the current connected elements
			Ap = {i: {j: Reaction('Ap_' + metF[i].formula + ',' + j.id) for j in rxnK} for i in met_fill_connect[eCC]}
			An = {i: {j: Reaction('An_' + metF[i].formula + ',' + j.id) for j in rxnK} for i in met_fill_connect[eCC]}
			for e in eCC:
				for j in rxnK:
					#RHS for each constraint: -sum(S_ij * m^known_ie) (obsolete)
					constraint[e][j]._bound = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements])
					constraint[e][j]._constraint_sense = 'E'
					#x_pos - x_neg for each constraint
					xp[e][j].add_metabolites({constraint[e][j]: 1})
					xn[e][j].add_metabolites({constraint[e][j]: -1})
					xp[e][j].lower_bound, xn[e][j].lower_bound, xp[e][j].upper_bound, xn[e][j].upper_bound = 0, 0, inf, inf
					#m * A_pos - m * A_neg for each constraint
					for i in met_fill_connect[eCC]:
						if e in metF[i].elements:
							Ap[i][j].add_metabolites({constraint[e][j]:  metF[i].elements[e]})
							An[i][j].add_metabolites({constraint[e][j]: -metF[i].elements[e]})
				for i in metU:
					# S_ij x m_ie for each i
					m[e][i].add_metabolites({constraint[e][j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK})
					m[e][i].upper_bound = inf
					m[e][i].lower_bound = neg_inf if e == 'Charge' else 0
			for i in met_fill_connect[eCC]:
				for j in rxnK:
					Ap[i][j].lower_bound, An[i][j].lower_bound, Ap[i][j].upper_bound, An[i][j].upper_bound = 0, 0, inf, inf

			#add reactions into the model
			metModelJ.add_reactions([m[e][i] for e in eCC for i in metU])
			metModelJ.add_reactions([xp[e][j] for e in eCC for j in rxnK])
			metModelJ.add_reactions([xn[e][j] for e in eCC for j in rxnK])
			if bool(met_fill_connect[eCC]):
				metModelJ.add_reactions([Ap[i][j] for i in met_fill_connect[eCC] for j in rxnK])
				metModelJ.add_reactions([An[i][j] for i in met_fill_connect[eCC] for j in rxnK])
            # the objective can be set as a dictionary of rxns {rxn_id: 1,...}
			objective_dict = {xp[e][j]: 1 for e in eCC for j in rxnK}
			objective_dict.update({xn[e][j]: 1 for e in eCC for j in rxnK})
			set_objective(metModelJ, objective_dict)
			#set RHS: -sum(S_ij * m^known_ie)
			for e in eCC:
				for j in rxnK:
					metModelJ.constraints[constraint[e][j].id].ub = inf #ub must be set to be > lb to avoid error
					metModelJ.constraints[constraint[e][j].id].lb, metModelJ.constraints[constraint[e][j].id].ub = constraint[e][j]._bound, constraint[e][j]._bound

			#Solve for minimum inconsistency
			sol = metModelJ.optimize(**kwargs)
			solStatJ = 'minIncon'
			infeasJ[solStatJ] = solution_infeasibility(metModelJ, sol)
			if sol.fluxes is None:
				boundJ[solStatJ] = {e: float('nan') for e in eCC}
			else:
				boundJ[solStatJ] = {e: sum([sol.fluxes[j.id] for j in chain(xp[e].values(), xn[e].values())]) for e in eCC}
			if not infeasJ[solStatJ] <= feasTol:
				#infeasible (should not happen)
				infeasJ['minFill'], infeasJ['minForm'] = inf, inf
				solConstrainJ = 'infeasible'
				solStatJ = 'infeasible'
				objJ['minIncon'], objJ['minFill'], objJ['minForm'] = (float('nan') for i in range(3))
			else:
				#Feasible. Store the solution
				solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[e][i].id] for i in metU} for e in eCC})
				solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[e][j].id] for j in rxnK} for e in eCC})
				solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[e][j].id] for j in rxnK} for e in eCC})
				solution[solStatJ]['Ap'].update({i: {j: sol.fluxes[Ap[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]})
				solution[solStatJ]['An'].update({i: {j: sol.fluxes[An[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]})
				objJ[solStatJ] = sol.objective_value  # .f deprecated since 2018 Oct
				#solution used to constrain the minimal formula problem
				solConstrainJ = 'minIncon'
				#minimize total adjustment if filling metabolites exist
				if bool(met_fill_connect[eCC]):
					#Add constraint to fix the total inconsistency for each element
					constraint_minIncon = {e: Metabolite('minIncon_'+e) for e in eCC}
					for e in eCC:
						#rounding to avoid infeasibility due to numerical issues
						constraint_minIncon[e]._bound = round(boundJ[solStatJ][e], digitRounded)
						constraint_minIncon[e]._constraint_sense = 'L'
						#sum(xp) + sum(xn) <= total inconsistency
						for j in rxnK:
							xp[e][j].add_metabolites({constraint_minIncon[e]: 1})
							xn[e][j].add_metabolites({constraint_minIncon[e]: 1})

						metModelJ.constraints[constraint_minIncon[e].id].lb = neg_inf
						metModelJ.constraints[constraint_minIncon[e].id].ub = round(boundJ[solStatJ][e], digitRounded)

					#reset the objective function
					objective_dict = {Ap[i][j]: 1 for i in met_fill_connect[eCC] for j in rxnK}
					objective_dict.update({An[i][j]: 1 for i in met_fill_connect[eCC] for j in rxnK})
					set_objective(metModelJ, objective_dict)
					solStatJ = 'minFill'
					eps0 = 1e-6
					while True:
						sol = metModelJ.optimize(**kwargs)
						infeasJ[solStatJ] = solution_infeasibility(metModelJ, sol)
						if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8:
							break
						eps0 *= 10
						for e in eCC:
							#rounding to avoid infeasibility due to numerical issues
							metModelJ.constraints[constraint_minIncon[e].id].ub = round(boundJ['minIncon'][e] * (1 + eps0), digitRounded)

					boundJ[solStatJ] = eps0
					if infeasJ[solStatJ] <= feasTol:
						#Feasible. Use this as the solution for constraining the minimal formula problem
						solConstrainJ = 'minFill'
						#Store the solution
						solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[e][i].id] for i in metU} for e in eCC})
						solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[e][j].id] for j in rxnK} for e in eCC})
						solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[e][j].id] for j in rxnK} for e in eCC})
						solution[solStatJ]['Ap'].update({i: {j: sol.fluxes[Ap[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]})
						solution[solStatJ]['An'].update({i: {j: sol.fluxes[An[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]})
						objJ[solStatJ] = sol.objective_value
					else:
						#infeasible, should not happen
						objJ[solStatJ] = float('nan')

					#prepare to compute minimal formulae
					eps0 = 1e-10
					for j in rxnK:
						for i in met_fill_connect[eCC]:
							#Fix the adjustment
							Ap[i][j].lower_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 - eps0), digitRounded)
							Ap[i][j].upper_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 + eps0), digitRounded)
							An[i][j].lower_bound = round(solution[solConstrainJ]['An'][i][j] * (1 - eps0), digitRounded)
							An[i][j].upper_bound = round(solution[solConstrainJ]['An'][i][j] * (1 + eps0), digitRounded)
						for e in eCC:
							#remove all coefficients on the constraints for total inconsistency
							tmp = xp[e][j]._metabolites.pop(constraint_minIncon[e])
							tmp = xn[e][j]._metabolites.pop(constraint_minIncon[e])
					for e in eCC:
						#then remove the constraints
						metModelJ.metabolites.remove(constraint_minIncon[e])
				else:
					#no filling metabolites
					infeasJ['minFill'], boundJ['minFill'] = 0, 0

				#compute minimal formulae
				eps0 = 1e-10
				for e in eCC:
					for j in rxnK:
						#fix the inconsistency
						xp[e][j].lower_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 - eps0), digitRounded)
						xp[e][j].upper_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 + eps0), digitRounded)
						xn[e][j].lower_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 - eps0), digitRounded)
						xn[e][j].upper_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 + eps0), digitRounded)

					if e == 'Charge':
						#add variables for the positive and negative part of charges
						chargePos = {i: Reaction('chargePos_' + i.id) for i in metU}
						chargeNeg = {i: Reaction('chargeNeg_' + i.id) for i in metU}
						constraint_chargeDecomp = {i: Metabolite('chargeDecomp_' + i.id) for i in metU}
						for i in metU:
							#constraint_chargeDecomp[i]: m_charge,i - chargePos_i + chargeNeg_i = 0
							m[e][i].add_metabolites({constraint_chargeDecomp[i]: 1})
							chargePos[i].add_metabolites({constraint_chargeDecomp[i]: -1})
							chargeNeg[i].add_metabolites({constraint_chargeDecomp[i]: 1})
							chargePos[i].lower_bound, chargePos[i].upper_bound = 0, inf
							chargeNeg[i].lower_bound, chargeNeg[i].upper_bound = 0, inf
							constraint_chargeDecomp[i]._bound, constraint_chargeDecomp[i]._constraint_sense = 0, 'E'
						metModelJ.add_metabolites(constraint_chargeDecomp.values())
						metModelJ.add_reactions(list(chargePos.values()) + list(chargeNeg.values()))

				#reset the objective function
				objective_dict = {m[e][i]: 1 for e in eCC if e != 'Charge' for i in metU}
				if 'Charge' in eCC:
					objective_dict.update({chargePos[i]: 1 for i in metU})
					objective_dict.update({chargeNeg[i]: 1 for i in metU})
				set_objective(metModelJ, objective_dict)

				#Solve for minimum formulae
				solStatPrev, solStatJ =solStatJ, 'minForm'
				while True:
					sol = metModelJ.optimize(**kwargs)
					infeasJ[solStatJ] = solution_infeasibility(metModelJ, sol)
					if infeasJ[solStatJ] <= feasTol or eps0 > 1e-5 + 1e-8:
						break
					eps0 *= 10
					#relax bounds, rounding to avoid infeasibility due to numerical issues
					for j in rxnK:
						for i in met_fill_connect[eCC]:
							Ap[i][j].lower_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 - eps0), digitRounded)
							Ap[i][j].upper_bound = round(solution[solConstrainJ]['Ap'][i][j] * (1 + eps0), digitRounded)
							An[i][j].lower_bound = round(solution[solConstrainJ]['An'][i][j] * (1 - eps0), digitRounded)
							An[i][j].upper_bound = round(solution[solConstrainJ]['An'][i][j] * (1 + eps0), digitRounded)
						for e in eCC:
							xp[e][j].lower_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 - eps0), digitRounded)
							xp[e][j].upper_bound = round(solution[solConstrainJ]['xp'][e][j] * (1 + eps0), digitRounded)
							xn[e][j].lower_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 - eps0), digitRounded)
							xn[e][j].upper_bound = round(solution[solConstrainJ]['xn'][e][j] * (1 + eps0), digitRounded)
				boundJ[solStatJ] = eps0
				if infeasJ[solStatJ] <= feasTol:
					#Feasible. Store the solution
					solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[e][i].id] for i in metU} for e in eCC})
					solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[e][j].id] for j in rxnK} for e in eCC})
					solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[e][j].id] for j in rxnK} for e in eCC})
					solution[solStatJ]['Ap'].update({i: {j: sol.fluxes[Ap[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]})
					solution[solStatJ]['An'].update({i: {j: sol.fluxes[An[i][j].id] for j in rxnK} for i in met_fill_connect[eCC]})
					objJ[solStatJ] = sol.objective_value
				else:
					#infeasible, should not happen
					solStatJ = solStatPrev
					objJ[solStatJ] = float('nan')

			#store data
			infeas[eCC] = infeasJ
			bound[eCC] = boundJ
			obj[eCC] = objJ
			met_model[eCC] = metModelJ
			sol_constrain[eCC] = solConstrainJ
			sol_stat[eCC] = solStatJ

		#summarize the final solution state
		if any([k == 'infeasible' for k in sol_stat.values()]):
			print('Failure: no feasible solution can be found.')
			solFinal = 'infeasible'
		else:
			solMixed = True
			for stat in ['minForm', 'minFill', 'minIncon']:
				if all([k == stat for k in sol_stat.values()]):
					solFinal, solMixed = stat, False
					break
			if solMixed:
				solFinal = 'mixed'

		#Get the resultant set of formulae. For each set of elements in ele_connect, choose the latest solution (minForm > minFill > minIncon), recorded in sol_stat.
		if solFinal != 'infeasible':
			formulae = {i: Formula('Mass0') for i in metU}
			for i in metU:
				for eCC in ele_connect:
					formulae[i].update_elements({e: round(solution[sol_stat[eCC]]['m'][e][i], digitRounded) for e in eCC})
		else:
			formulae = {i: Formula() for i in metU}

		formulae.update(metK)
		mip_info = min_incon_parsi_info()
		mip_info.formulae, mip_info.infeas, mip_info.bound, mip_info.obj, mip_info.solution, \
		mip_info.met_model, mip_info.final, mip_info.sol_constrain, mip_info.sol_stat \
		= formulae, infeas, bound, obj, solution, met_model, solFinal, sol_constrain, sol_stat

		return mip_info
Ejemplo n.º 26
0
	def min_incon_parsi_mwRange(self, metInterest, **kwargs):
		'''The main step called by compute_met_range to find the range for the molecular weight
		Return min_incon_parsi_info object summarizing the results of solving MIP.
			formulae: (formula for min MW, formula max MW)
			mw_range: (min MW, max MW)
			rhs: {e: {i: RHS[i] for all metabolite i}} the RHS value in the MIP problem for each element solved. (compute_met_range only)
			infeas: infeasibility of each solve
			bound: bound used for total inconsistency or the relaxation value eps0 for each solve
			obj: objective function value for each solve
			solution: solution values for each type of variables (m, xp, xn)
			met_model: the MIP problem solved for each element as a cobra model. Same model but different rhs for different elements.
			final: the final status of the solution
			sol_stat: solution status for each element/connected set of elements
		'''
		inf, neg_inf = self.infinity, self.negative_infinity
		pre = self.pre
		metK, metU, rxnK, ele, feasTol, digitRounded \
		= pre.met_known, pre.met_unknown, pre.rxn_known, pre.ele, pre.feasTol, pre.digitRounded
		model = self.model
		#handle metInterest
		if isinstance(metInterest,type(model.metabolites[0])):
			metI = metInterest
		elif isinstance(metInterest, str):
			metI = model.metabolites.get_by_id(metInterest)
		if metI in metK:
			print("%s in the input is already known." %metI.id)
			mip_info = min_incon_parsi_info()
			mip_info.mw_range = (metK[metI].mw, metK[metI].mw)
			return mip_info

		print('Find the range for the molecular weight of %s ... %s' %(metI.id, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))

		infeas, bound, solution, sol_stat, obj, rhs = ({} for i in range(6))
		for k in ['minIncon', 'minMw', 'maxMw']: #pre-assignment
			solution[k] = {'m': {}, 'xp': {}, 'xn': {}}
		ct = 0
		#The optimization problem for each element is the same except the RHS
		met_model = Model('min/max Mw of ' + metI.id)
		met_model.solver = self.__solver
		constraint = {j: Metabolite(j.id) for j in rxnK}
		m = {i: Reaction('m_' + i.id) for i in metU}
		xp = {j: Reaction('xp_' + j.id) for j in rxnK}
		xn = {j: Reaction('xn_' + j.id) for j in rxnK}
		for j in rxnK:
			xp[j].add_metabolites({constraint[j]: 1})
			xn[j].add_metabolites({constraint[j]: -1})
			xp[j].lower_bound, xn[j].lower_bound, xp[j].upper_bound, xn[j].upper_bound = 0, 0, inf, inf
			constraint[j]._constraint_sense = 'E'

		for i in metU:
			# S_ij x m_ie for each i
			m[i].add_metabolites({constraint[j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK})
			m[i].upper_bound = inf

		#Add constraint to fix the total inconsistency for each element
		constraint_minIncon = Metabolite('minIncon')
		constraint_minIncon._bound = inf
		constraint_minIncon._constraint_sense = 'L'
		for j in rxnK:
			xp[j].add_metabolites({constraint_minIncon: 1})
			xn[j].add_metabolites({constraint_minIncon: 1})

		met_model.add_reactions([m[i] for i in metU])
		met_model.add_reactions([xp[j] for j in rxnK])
		met_model.add_reactions([xn[j] for j in rxnK])

		met_model.constraints[constraint_minIncon.id].lb = neg_inf #to avoid error

		objective_dict_minIncon = {xp[j]: 1 for j in rxnK}
		objective_dict_minIncon.update({xn[j]: 1 for j in rxnK})


		for e in ele:
			ct += 1
			print("Optimizing for %d / %d element ... %s" %(ct, len(ele), datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
			# metModelJ = Model('min/max ' + e)
			# metModelJ.solver = self.__solver
			infeasJ, boundJ, objJ, rhsJ = {}, {}, {}, {}
			# constraint = {j: Metabolite(j.id + ',' + e) for j in rxnK}
			# m = {i: Reaction('m_' + i.id + ',' + e) for i in metU}
			# xp = {j: Reaction('xp_' + j.id + ',' + e) for j in rxnK}
			# xn = {j: Reaction('xn_' + j.id + ',' + e) for j in rxnK}
			# for j in rxnK:
				#RHS for each constraint: -sum(S_ij * m^known_ie) (obsolete)
				# constraint[j]._bound = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements])
				# constraint[j]._constraint_sense = 'E'
				#x_pos - x_neg for each constraint
				# xp[j].add_metabolites({constraint[j]: 1})
				# xn[j].add_metabolites({constraint[j]: -1})
				# xp[j].lower_bound, xn[j].lower_bound, xp[j].upper_bound, xn[j].upper_bound = 0, 0, inf, inf
			for i in metU:
				# # S_ij x m_ie for each i
				# m[i].add_metabolites({constraint[j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK})
				# m[i].upper_bound = inf
				m[i].lower_bound = neg_inf if e == 'Charge' else 0
			#add reactions into the model
			# metModelJ.add_reactions([m[i] for i in metU])
			# metModelJ.add_reactions([xp[j] for j in rxnK])
			# metModelJ.add_reactions([xn[j] for j in rxnK])
			#set the objective function
			# objective_dict = {xp[j]: 1 for j in rxnK}
			# objective_dict.update({xn[j]: 1 for j in rxnK})
			met_model.constraints[constraint_minIncon.id].ub = inf
			set_objective(met_model, objective_dict_minIncon)
			#set RHS: -sum(S_ij * m^known_ie)
			for j in rxnK:
				met_model.constraints[constraint[j].id].ub = inf #ub must be set to be > lb to avoid error
				rhsJ[j] = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements])
				met_model.constraints[constraint[j].id].lb, met_model.constraints[constraint[j].id].ub = rhsJ[j], rhsJ[j]
				constraint[j]._bound = rhsJ[j]

			#Solve for minimum inconsistency
			kwargs['objective_sense'] = 'minimize'
			sol = met_model.optimize(**kwargs)
			solStatJ = 'minIncon'
			infeasJ[solStatJ] = solution_infeasibility(met_model, sol)
			if sol.fluxes is None:
				boundJ[solStatJ] = float('nan')
			else:
				boundJ[solStatJ] = sum([sol.fluxes[j.id] for j in chain(xp.values(), xn.values())])
			if not infeasJ[solStatJ] <= feasTol:
				#infeasible (should not happen)
				infeasJ['minMw'], infeasJ['maxMw'] = inf, inf
				solStatJ = 'infeasible'
				objJ['minIncon'], objJ['minMw'], objJ['maxMw'] = (float('nan') for i in range(3))
				for s in ['minIncon','minMw','maxMw']:
					solution[s]['m'][e] = {i: float('nan') for i in metU}
					solution[s]['xp'][e] = {j: float('nan') for j in rxnK}
					solution[s]['xn'][e] = {j: float('nan') for j in rxnK}
			else:
				#Feasible. Store the solution
				solution[solStatJ]['m'][e] = {i: sol.fluxes[m[i].id] for i in metU}
				solution[solStatJ]['xp'][e] = {j: sol.fluxes[xp[j].id] for j in rxnK}
				solution[solStatJ]['xn'][e] = {j: sol.fluxes[xn[j].id] for j in rxnK}
				objJ[solStatJ] = sol.objective_value
				# #Add constraint to fix the total inconsistency for each element
				# constraint_minIncon = Metabolite('minIncon_'+e)
				# constraint_minIncon._bound = round(boundJ['minIncon'], digitRounded)
				# constraint_minIncon._constraint_sense = 'L'
				# for j in rxnK:
				# 	xp[j].add_metabolites({constraint_minIncon: 1})
				# 	xn[j].add_metabolites({constraint_minIncon: 1})

				# met_model.constraints[constraint_minIncon.id].lb = neg_inf #to avoid error
				met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'], digitRounded)
				#reset the objective function to minimize molecular weight
				objective_dict = {m[metI]: Formula(formula=e).mw}
				set_objective(met_model, objective_dict)
				solStatJ = 'minMw'
				kwargs['objective_sense'] = 'minimize'
				eps0 = 1e-6
				while True:
					sol = met_model.optimize(**kwargs)
					infeasJ[solStatJ] = solution_infeasibility(met_model, sol)
					if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8:
						break
					eps0 *= 10
					#rounding to avoid infeasibility due to numerical issues
					met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'] * (1 + eps0), digitRounded)

				boundJ[solStatJ] = eps0
				if infeasJ[solStatJ] <= feasTol:
					#Feasible. Store the solution
					solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[i].id] for i in metU}})
					solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[j].id] for j in rxnK}})
					solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[j].id] for j in rxnK}})
					objJ[solStatJ] = sol.objective_value
				else:
					#infeasible, should not happen
					objJ[solStatJ] = float('nan')
					solution['minMw']['m'][e] = {i: float('nan') for i in metU}
					solution['minMw']['xp'][e] = {j: float('nan') for j in rxnK}
					solution['minMw']['xn'][e] = {j: float('nan') for j in rxnK}

				#maximize molecular weight
				solStatJ = 'maxMw'
				#reset the bound for total inconsistency
				met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'], digitRounded)
				kwargs['objective_sense'] = 'maximize'
				eps0 = 1e-6
				while True:
					sol = met_model.optimize(**kwargs)
					infeasJ[solStatJ] = solution_infeasibility(met_model, sol)
					if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8:
						break
					eps0 *= 10
					#rounding to avoid infeasibility due to numerical issues
					met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'] * (1 + eps0), digitRounded)

				boundJ[solStatJ] = eps0
				if infeasJ[solStatJ] <= feasTol:
					#Feasible. Store the solution
					solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[i].id] for i in metU}})
					solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[j].id] for j in rxnK}})
					solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[j].id] for j in rxnK}})
					objJ[solStatJ] = sol.objective_value
				else:
					#infeasible, should not happen
					objJ[solStatJ] = float('nan')
					solution['maxMw']['m'][e] = {i: float('nan') for i in metU}
					solution['maxMw']['xp'][e] = {j: float('nan') for j in rxnK}
					solution['maxMw']['xn'][e] = {j: float('nan') for j in rxnK}

			#store data
			infeas[e] = infeasJ
			bound[e] = boundJ
			obj[e] = objJ
			# met_model[e] = met_model
			sol_stat[e] = solStatJ
			rhs[e] = rhsJ

		#summarize the final solution state
		if any([k == 'infeasible' for k in sol_stat.values()]):
			print('Failure: no feasible solution can be found.')
			solFinal = 'infeasible'
		else:
			if all([obj[e]['minMw'] == obj[e]['minMw'] for e in ele]):
				if all([obj[e]['maxMw'] == obj[e]['maxMw'] for e in ele]):
					solFinal = 'minMw + maxMw'
				else:
					solFinal = 'minMw only'
			else:
				if all([obj[e]['maxMw'] == obj[e]['maxMw'] for e in ele]):
					solFinal = 'maxMw only'
				else:
					solFinal = 'minIncon'

		mip_info = min_incon_parsi_info()
		mip_info.infeas, mip_info.bound, mip_info.obj, mip_info.solution, \
		mip_info.met_model, mip_info.final, mip_info.sol_stat \
		= infeas, bound, obj, solution, met_model, solFinal, sol_stat
		mip_info.rhs = rhs
		mip_info.mw_range = (sum([obj[e]['minMw'] for e in ele]), sum([obj[e]['maxMw'] for e in ele]))
		mip_info.formulae = tuple([formula_dict2str({e: solution[s]['m'][e][metI] for e in ele}) for s in ['minMw', 'maxMw']])
		return mip_info
Ejemplo n.º 27
0
def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
                                      legacy_metabolite=False,
                                      print_time=False, use_hyphens=False):
    """convert an SBML XML file into a cobra.Model object.

    Supports SBML Level 2 Versions 1 and 4.  The function will detect if the
    SBML fbc package is used in the file and run the converter if the fbc
    package is used.

    Parameters
    ----------
    sbml_filename: string
    old_sbml: bool
        Set to True if the XML file has metabolite formula appended to
        metabolite names. This was a poorly designed artifact that persists in
        some models.
    legacy_metabolite: bool
        If True then assume that the metabolite id has the compartment id
         appended after an underscore (e.g. _c for cytosol). This has not been
         implemented but will be soon.
    print_time: bool
         deprecated
    use_hyphens: bool
        If True, double underscores (__) in an SBML ID will be converted to
        hyphens

    Returns
    -------
    Model : The parsed cobra model
    """
    if not libsbml:
        raise ImportError('create_cobra_model_from_sbml_file '
                          'requires python-libsbml')

    __default_lower_bound = -1000
    __default_upper_bound = 1000
    __default_objective_coefficient = 0
    # Ensure that the file exists
    if not isfile(sbml_filename):
        raise IOError('Your SBML file is not found: %s' % sbml_filename)
    # Expressions to change SBML Ids to Palsson Lab Ids
    metabolite_re = re.compile('^M_')
    reaction_re = re.compile('^R_')
    compartment_re = re.compile('^C_')
    if print_time:
        warn("print_time is deprecated", DeprecationWarning)
    model_doc = libsbml.readSBML(sbml_filename)
    if model_doc.getPlugin("fbc") is not None:
        from libsbml import ConversionProperties, LIBSBML_OPERATION_SUCCESS
        conversion_properties = ConversionProperties()
        conversion_properties.addOption(
            "convert fbc to cobra", True, "Convert FBC model to Cobra model")
        result = model_doc.convert(conversion_properties)
        if result != LIBSBML_OPERATION_SUCCESS:
            raise Exception("Conversion of SBML+fbc to COBRA failed")
    sbml_model = model_doc.getModel()
    sbml_model_id = sbml_model.getId()
    sbml_species = sbml_model.getListOfSpecies()
    sbml_reactions = sbml_model.getListOfReactions()
    sbml_compartments = sbml_model.getListOfCompartments()
    compartment_dict = dict([(compartment_re.split(x.getId())[-1], x.getName())
                             for x in sbml_compartments])
    if legacy_metabolite:
        # Deal with the palsson lab appending the compartment id to the
        # metabolite id
        new_dict = {}
        for the_id, the_name in compartment_dict.items():
            if the_name == '':
                new_dict[the_id[0].lower()] = the_id
            else:
                new_dict[the_id] = the_name
        compartment_dict = new_dict
        legacy_compartment_converter = dict(
            [(v, k) for k, v in iteritems(compartment_dict)])

    cobra_model = Model(sbml_model_id)
    metabolites = []
    metabolite_dict = {}
    # Convert sbml_metabolites to cobra.Metabolites
    for sbml_metabolite in sbml_species:
        # Skip sbml boundary species
        if sbml_metabolite.getBoundaryCondition():
            continue

        if (old_sbml or legacy_metabolite) and \
                sbml_metabolite.getId().endswith('_b'):
            # Deal with incorrect sbml from bigg.ucsd.edu
            continue
        tmp_metabolite = Metabolite()
        metabolite_id = tmp_metabolite.id = sbml_metabolite.getId()
        tmp_metabolite.compartment = compartment_re.split(
            sbml_metabolite.getCompartment())[-1]
        if legacy_metabolite:
            if tmp_metabolite.compartment not in compartment_dict:
                tmp_metabolite.compartment = legacy_compartment_converter[
                    tmp_metabolite.compartment]
            tmp_metabolite.id = parse_legacy_id(
                tmp_metabolite.id, tmp_metabolite.compartment,
                use_hyphens=use_hyphens)
        if use_hyphens:
            tmp_metabolite.id = metabolite_re.split(
                tmp_metabolite.id)[-1].replace('__', '-')
        else:
            # Just in case the SBML ids are ill-formed and use -
            tmp_metabolite.id = metabolite_re.split(
                tmp_metabolite.id)[-1].replace('-', '__')
        tmp_metabolite.name = sbml_metabolite.getName()
        tmp_formula = ''
        tmp_metabolite.notes = parse_legacy_sbml_notes(
            sbml_metabolite.getNotesString())
        if sbml_metabolite.isSetCharge():
            tmp_metabolite.charge = sbml_metabolite.getCharge()
        if "CHARGE" in tmp_metabolite.notes:
            note_charge = tmp_metabolite.notes["CHARGE"][0]
            try:
                note_charge = float(note_charge)
                if note_charge == int(note_charge):
                    note_charge = int(note_charge)
            except:
                warn("charge of %s is not a number (%s)" %
                     (tmp_metabolite.id, str(note_charge)))
            else:
                if ((tmp_metabolite.charge is None) or
                        (tmp_metabolite.charge == note_charge)):
                    tmp_metabolite.notes.pop("CHARGE")
                    # set charge to the one from notes if not assigend before
                    # the same
                    tmp_metabolite.charge = note_charge
                else:  # tmp_metabolite.charge != note_charge
                    msg = "different charges specified for %s (%d and %d)"
                    msg = msg % (tmp_metabolite.id,
                                 tmp_metabolite.charge, note_charge)
                    warn(msg)
                    # Chances are a 0 note charge was written by mistake. We
                    # will default to the note_charge in this case.
                    if tmp_metabolite.charge == 0:
                        tmp_metabolite.charge = note_charge

        for the_key in tmp_metabolite.notes.keys():
            if the_key.lower() == 'formula':
                tmp_formula = tmp_metabolite.notes.pop(the_key)[0]
                break
        if tmp_formula == '' and old_sbml:
            tmp_formula = tmp_metabolite.name.split('_')[-1]
            tmp_metabolite.name = tmp_metabolite.name[:-len(tmp_formula) - 1]
        tmp_metabolite.formula = tmp_formula
        metabolite_dict.update({metabolite_id: tmp_metabolite})
        metabolites.append(tmp_metabolite)
    cobra_model.add_metabolites(metabolites)

    # Construct the vectors and matrices for holding connectivity and numerical
    # info to feed to the cobra toolbox.
    # Always assume steady state simulations so b is set to 0
    cobra_reaction_list = []
    coefficients = {}
    for sbml_reaction in sbml_reactions:
        if use_hyphens:
            # Change the ids to match conventions used by the Palsson lab.
            reaction = Reaction(reaction_re.split(
                sbml_reaction.getId())[-1].replace('__', '-'))
        else:
            # Just in case the SBML ids are ill-formed and use -
            reaction = Reaction(reaction_re.split(
                sbml_reaction.getId())[-1].replace('-', '__'))
        cobra_reaction_list.append(reaction)
        # reaction.exchange_reaction = 0
        reaction.name = sbml_reaction.getName()
        cobra_metabolites = {}
        # Use the cobra.Metabolite class here
        for sbml_metabolite in sbml_reaction.getListOfReactants():
            tmp_metabolite_id = sbml_metabolite.getSpecies()
            # This deals with boundary metabolites
            if tmp_metabolite_id in metabolite_dict:
                tmp_metabolite = metabolite_dict[tmp_metabolite_id]
                cobra_metabolites[tmp_metabolite] = - \
                    sbml_metabolite.getStoichiometry()
        for sbml_metabolite in sbml_reaction.getListOfProducts():
            tmp_metabolite_id = sbml_metabolite.getSpecies()
            # This deals with boundary metabolites
            if tmp_metabolite_id in metabolite_dict:
                tmp_metabolite = metabolite_dict[tmp_metabolite_id]
                # Handle the case where the metabolite was specified both
                # as a reactant and as a product.
                if tmp_metabolite in cobra_metabolites:
                    warn("%s appears as a reactant and product %s" %
                         (tmp_metabolite_id, reaction.id))
                    cobra_metabolites[
                        tmp_metabolite] += sbml_metabolite.getStoichiometry()
                    # if the combined stoichiometry is 0, remove the metabolite
                    if cobra_metabolites[tmp_metabolite] == 0:
                        cobra_metabolites.pop(tmp_metabolite)
                else:
                    cobra_metabolites[
                        tmp_metabolite] = sbml_metabolite.getStoichiometry()
        # check for nan
        for met, v in iteritems(cobra_metabolites):
            if isnan(v) or isinf(v):
                warn("invalid value %s for metabolite '%s' in reaction '%s'" %
                     (str(v), met.id, reaction.id))
        reaction.add_metabolites(cobra_metabolites)
        # Parse the kinetic law info here.
        parameter_dict = {}
        # If lower and upper bounds are specified in the Kinetic Law then
        # they override the sbml reversible attribute.  If they are not
        # specified then the bounds are determined by getReversible.
        if not sbml_reaction.getKineticLaw():

            if sbml_reaction.getReversible():
                parameter_dict['lower_bound'] = __default_lower_bound
                parameter_dict['upper_bound'] = __default_upper_bound
            else:
                # Assume that irreversible reactions only proceed from left to
                # right.
                parameter_dict['lower_bound'] = 0
                parameter_dict['upper_bound'] = __default_upper_bound

            parameter_dict[
                'objective_coefficient'] = __default_objective_coefficient
        else:
            for sbml_parameter in \
                    sbml_reaction.getKineticLaw().getListOfParameters():
                parameter_dict[
                    sbml_parameter.getId().lower()] = sbml_parameter.getValue()

        if 'lower_bound' in parameter_dict:
            reaction.lower_bound = parameter_dict['lower_bound']
        elif 'lower bound' in parameter_dict:
            reaction.lower_bound = parameter_dict['lower bound']
        elif sbml_reaction.getReversible():
            reaction.lower_bound = __default_lower_bound
        else:
            reaction.lower_bound = 0

        if 'upper_bound' in parameter_dict:
            reaction.upper_bound = parameter_dict['upper_bound']
        elif 'upper bound' in parameter_dict:
            reaction.upper_bound = parameter_dict['upper bound']
        else:
            reaction.upper_bound = __default_upper_bound

        objective_coefficient = parameter_dict.get(
            'objective_coefficient', parameter_dict.get(
                'objective_coefficient', __default_objective_coefficient))
        if objective_coefficient != 0:
            coefficients[reaction] = objective_coefficient

        # ensure values are not set to nan or inf
        if isnan(reaction.lower_bound) or isinf(reaction.lower_bound):
            reaction.lower_bound = __default_lower_bound
        if isnan(reaction.upper_bound) or isinf(reaction.upper_bound):
            reaction.upper_bound = __default_upper_bound

        reaction_note_dict = parse_legacy_sbml_notes(
            sbml_reaction.getNotesString())
        # Parse the reaction notes.
        # POTENTIAL BUG: DEALING WITH LEGACY 'SBML' THAT IS NOT IN A
        # STANDARD FORMAT
        # TODO: READ IN OTHER NOTES AND GIVE THEM A reaction_ prefix.
        # TODO: Make sure genes get added as objects
        if 'GENE ASSOCIATION' in reaction_note_dict:
            rule = reaction_note_dict['GENE ASSOCIATION'][0]
            try:
                rule.encode('ascii')
            except (UnicodeEncodeError, UnicodeDecodeError):
                warn("gene_reaction_rule '%s' is not ascii compliant" % rule)
            if rule.startswith("&quot;") and rule.endswith("&quot;"):
                rule = rule[6:-6]
            reaction.gene_reaction_rule = rule
            if 'GENE LIST' in reaction_note_dict:
                reaction.systematic_names = reaction_note_dict['GENE LIST'][0]
            elif ('GENES' in reaction_note_dict and
                  reaction_note_dict['GENES'] != ['']):
                reaction.systematic_names = reaction_note_dict['GENES'][0]
            elif 'LOCUS' in reaction_note_dict:
                gene_id_to_object = dict([(x.id, x) for x in reaction._genes])
                for the_row in reaction_note_dict['LOCUS']:
                    tmp_row_dict = {}
                    the_row = 'LOCUS:' + the_row.lstrip('_').rstrip('#')
                    for the_item in the_row.split('#'):
                        k, v = the_item.split(':')
                        tmp_row_dict[k] = v
                    tmp_locus_id = tmp_row_dict['LOCUS']
                    if 'TRANSCRIPT' in tmp_row_dict:
                        tmp_locus_id = tmp_locus_id + \
                                       '.' + tmp_row_dict['TRANSCRIPT']

                    if 'ABBREVIATION' in tmp_row_dict:
                        gene_id_to_object[tmp_locus_id].name = tmp_row_dict[
                            'ABBREVIATION']

        if 'SUBSYSTEM' in reaction_note_dict:
            reaction.subsystem = reaction_note_dict.pop('SUBSYSTEM')[0]

        reaction.notes = reaction_note_dict

    # Now, add all of the reactions to the model.
    cobra_model.id = sbml_model.getId()
    # Populate the compartment list - This will be done based on
    # cobra.Metabolites in cobra.Reactions in the future.
    cobra_model.compartments = compartment_dict

    cobra_model.add_reactions(cobra_reaction_list)
    set_objective(cobra_model, coefficients)
    return cobra_model
Ejemplo n.º 28
0
def relax_dgo(tmodel, reactions_to_ignore=(), solver=None):
    """
    :param t_tmodel:
    :type t_tmodel: pytfa.thermo.ThermoModel:
    :param reactions_to_ignore: Iterable of reactions that should not be relaxed
    :param solver: solver to use (e.g. 'optlang-glpk', 'optlang-cplex',
        'optlang-gurobi'
    :return: a cobra_model with relaxed bounds on standard Gibbs free energy
    """

    if solver is None:
        solver = tmodel.solver.interface

    # Create a copy of the cobra_model on which we will perform the slack addition
    slack_model = deepcopy(tmodel)
    slack_model.solver = solver
    slack_model.name = 'SlackModel ' + tmodel.name
    slack_model.id = 'SlackModel_' + tmodel.id

    # Create a copy that will receive the relaxation
    relaxed_model = deepcopy(tmodel)
    relaxed_model.solver = solver
    relaxed_model.name = 'RelaxedModel ' + tmodel.name
    relaxed_model.id = 'RelaxedModel_' + tmodel.id

    # Ensure the lazy updates are all done
    slack_model.repair()
    relaxed_model.repair()

    # Do not relax if cobra_model is already optimal
    # try:
    #     solution = tmodel.optimize()
    # except SolverError as SE:
    #     status = tmodel.solver.status
    #     tmodel.logger.error(SE)
    #     tmodel.logger.warning('Solver status: {}'.format(status))
    # if tmodel.solver.status == OPTIMAL:
    #     raise Exception('Model is already optimal')

    # Find variables that represent standard Gibbs Energy change
    my_dgo = relaxed_model.get_variables_of_type(DeltaGstd)

    # Find constraints that represent negativity of Gibbs Energy change
    my_neg_dg = slack_model.get_constraints_of_type(NegativeDeltaG)

    changes = OrderedDict()
    objective_symbols = []

    slack_model.logger.info('Adding slack constraints')
    hooks = dict()

    for this_neg_dg in my_neg_dg:

        # If there is no thermo, or relaxation forbidden, pass
        if this_neg_dg.id in reactions_to_ignore or this_neg_dg.id not in my_dgo:
            continue

        # If there is no thermo, or relaxation forbidden, pass
        if this_neg_dg.id in reactions_to_ignore or this_neg_dg.id not in my_dgo:
            continue

        # Create the negative and positive slack variables
        neg_slack = slack_model.add_variable(NegSlackVariable,
                                             this_neg_dg.reaction,
                                             lb=0,
                                             ub=BIGM_DG)
        pos_slack = slack_model.add_variable(PosSlackVariable,
                                             this_neg_dg.reaction,
                                             lb=0,
                                             ub=BIGM_DG)

        subs_dict = {
            k: slack_model.variables.get(k.name)
            for k in this_neg_dg.constraint.variables
        }

        # Create the new constraint by adding the slack variables to the
        # negative delta G constraint (from the initial cobra_model)
        new_expr = this_neg_dg.constraint.expression.subs(subs_dict)
        new_expr += (pos_slack - neg_slack)

        this_reaction = this_neg_dg.reaction
        # Remove former constraint to override it
        slack_model.remove_constraint(slack_model._cons_dict[this_neg_dg.name])

        # Add the new variant
        slack_model.add_constraint(NegativeDeltaG,
                                   this_reaction,
                                   expr=new_expr,
                                   lb=0,
                                   ub=0)

        # Update the objective with the new variables
        objective_symbols += [neg_slack, pos_slack]

    # objective = chunk_sum(objective_symbols)
    objective = symbol_sum(objective_symbols)

    # Change the objective to minimize slack
    set_objective(slack_model, objective)

    # Update variables and constraints references
    slack_model.repair()

    slack_model.logger.info('Optimizing slack model')
    # Relax
    slack_model.objective.direction = 'min'
    relaxation = slack_model.optimize()

    # Extract the relaxation values from the solution, by type
    relaxed_model.logger.info('Extracting relaxation')
    my_neg_slacks = slack_model.get_variables_of_type(NegSlackVariable)
    my_pos_slacks = slack_model.get_variables_of_type(PosSlackVariable)

    neg_slack_values = get_solution_value_for_variables(
        relaxation, my_neg_slacks)
    pos_slack_values = get_solution_value_for_variables(
        relaxation, my_pos_slacks)

    epsilon = relaxed_model.solver.configuration.tolerances.feasibility

    for this_reaction in relaxed_model.reactions:
        # No thermo, or relaxation forbidden
        if this_reaction.id in reactions_to_ignore or this_reaction.id not in my_dgo:
            continue

        # Get the standard delta G variable
        the_dgo = my_dgo.get_by_id(this_reaction.id)

        # Get the relaxation
        dgo_delta_lb = \
            neg_slack_values[my_neg_slacks \
                .get_by_id(this_reaction.id).name]
        dgo_delta_ub = \
            pos_slack_values[my_pos_slacks \
                .get_by_id(this_reaction.id).name]

        # Apply reaction delta G standard bound change
        if dgo_delta_lb > 0 or dgo_delta_ub > 0:

            # Store previous values
            previous_dgo_lb = the_dgo.variable.lb
            previous_dgo_ub = the_dgo.variable.ub

            # Apply change
            the_dgo.variable.lb -= (dgo_delta_lb + epsilon)
            the_dgo.variable.ub += (dgo_delta_ub + epsilon)

            # If needed, store that in a report table
            changes[this_reaction.id] = [
                previous_dgo_lb, previous_dgo_ub, dgo_delta_lb, dgo_delta_ub,
                the_dgo.variable.lb, the_dgo.variable.ub
            ]

    relaxed_model.logger.info('Testing relaxation')

    relaxed_model.optimize()

    # Format relaxation
    relax_table = pd.DataFrame.from_dict(changes, orient='index')
    relax_table.columns = [
        'lb_in', 'ub_in', 'lb_change', 'ub_change', 'lb_out', 'ub_out'
    ]

    relaxed_model.logger.info('\n' + relax_table.__str__())

    return relaxed_model, slack_model, relax_table
Ejemplo n.º 29
0
def parse_xml_into_model(xml, number=float):
    xml_model = xml.find(ns("sbml:model"))
    if get_attrib(xml_model, "fbc:strict") != "true":
        warn('loading SBML model without fbc:strict="true"')

    model_id = get_attrib(xml_model, "id")
    model = Model(model_id)
    model.name = xml_model.get("name")

    model.compartments = {c.get("id"): c.get("name") for c in
                          xml_model.findall(COMPARTMENT_XPATH)}
    # add metabolites
    for species in xml_model.findall(SPECIES_XPATH % 'false'):
        met = get_attrib(species, "id", require=True)
        met = Metabolite(clip(met, "M_"))
        met.name = species.get("name")
        annotate_cobra_from_sbml(met, species)
        met.compartment = species.get("compartment")
        met.charge = get_attrib(species, "fbc:charge", int)
        met.formula = get_attrib(species, "fbc:chemicalFormula")
        model.add_metabolites([met])
    # Detect boundary metabolites - In case they have been mistakenly
    # added. They should not actually appear in a model
    boundary_metabolites = {clip(i.get("id"), "M_")
                            for i in xml_model.findall(SPECIES_XPATH % 'true')}

    # add genes
    for sbml_gene in xml_model.iterfind(GENES_XPATH):
        gene_id = get_attrib(sbml_gene, "fbc:id").replace(SBML_DOT, ".")
        gene = Gene(clip(gene_id, "G_"))
        gene.name = get_attrib(sbml_gene, "fbc:name")
        if gene.name is None:
            gene.name = get_attrib(sbml_gene, "fbc:label")
        annotate_cobra_from_sbml(gene, sbml_gene)
        model.genes.append(gene)

    def process_gpr(sub_xml):
        """recursively convert gpr xml to a gpr string"""
        if sub_xml.tag == OR_TAG:
            return "( " + ' or '.join(process_gpr(i) for i in sub_xml) + " )"
        elif sub_xml.tag == AND_TAG:
            return "( " + ' and '.join(process_gpr(i) for i in sub_xml) + " )"
        elif sub_xml.tag == GENEREF_TAG:
            gene_id = get_attrib(sub_xml, "fbc:geneProduct", require=True)
            return clip(gene_id, "G_")
        else:
            raise Exception("unsupported tag " + sub_xml.tag)

    bounds = {bound.get("id"): get_attrib(bound, "value", type=number)
              for bound in xml_model.iterfind(BOUND_XPATH)}
    # add reactions
    reactions = []
    for sbml_reaction in xml_model.iterfind(
            ns("sbml:listOfReactions/sbml:reaction")):
        reaction = get_attrib(sbml_reaction, "id", require=True)
        reaction = Reaction(clip(reaction, "R_"))
        reaction.name = sbml_reaction.get("name")
        annotate_cobra_from_sbml(reaction, sbml_reaction)
        lb_id = get_attrib(sbml_reaction, "fbc:lowerFluxBound", require=True)
        ub_id = get_attrib(sbml_reaction, "fbc:upperFluxBound", require=True)
        try:
            reaction.upper_bound = bounds[ub_id]
            reaction.lower_bound = bounds[lb_id]
        except KeyError as e:
            raise CobraSBMLError("No constant bound with id '%s'" % str(e))
        reactions.append(reaction)

        stoichiometry = defaultdict(lambda: 0)
        for species_reference in sbml_reaction.findall(
                ns("sbml:listOfReactants/sbml:speciesReference")):
            met_name = clip(species_reference.get("species"), "M_")
            stoichiometry[met_name] -= \
                number(species_reference.get("stoichiometry"))
        for species_reference in sbml_reaction.findall(
                ns("sbml:listOfProducts/sbml:speciesReference")):
            met_name = clip(species_reference.get("species"), "M_")
            stoichiometry[met_name] += \
                get_attrib(species_reference, "stoichiometry",
                           type=number, require=True)
        # needs to have keys of metabolite objects, not ids
        object_stoichiometry = {}
        for met_id in stoichiometry:
            if met_id in boundary_metabolites:
                warn("Boundary metabolite '%s' used in reaction '%s'" %
                     (met_id, reaction.id))
                continue
            try:
                metabolite = model.metabolites.get_by_id(met_id)
            except KeyError:
                warn("ignoring unknown metabolite '%s' in reaction %s" %
                     (met_id, reaction.id))
                continue
            object_stoichiometry[metabolite] = stoichiometry[met_id]
        reaction.add_metabolites(object_stoichiometry)
        # set gene reaction rule
        gpr_xml = sbml_reaction.find(GPR_TAG)
        if gpr_xml is not None and len(gpr_xml) != 1:
            warn("ignoring invalid geneAssociation for " + repr(reaction))
            gpr_xml = None
        gpr = process_gpr(gpr_xml[0]) if gpr_xml is not None else ''
        # remove outside parenthesis, if any
        if gpr.startswith("(") and gpr.endswith(")"):
            gpr = gpr[1:-1].strip()
        gpr = gpr.replace(SBML_DOT, ".")
        reaction.gene_reaction_rule = gpr
    try:
        model.add_reactions(reactions)
    except ValueError as e:
        warn(str(e))

    # objective coefficients are handled after all reactions are added
    obj_list = xml_model.find(ns("fbc:listOfObjectives"))
    if obj_list is None:
        warn("listOfObjectives element not found")
        return model
    target_objective_id = get_attrib(obj_list, "fbc:activeObjective")
    target_objective = obj_list.find(
        ns("fbc:objective[@fbc:id='{}']".format(target_objective_id)))
    obj_direction_long = get_attrib(target_objective, "fbc:type")
    obj_direction = LONG_SHORT_DIRECTION[obj_direction_long]

    obj_query = OBJECTIVES_XPATH % target_objective_id
    coefficients = {}
    for sbml_objective in obj_list.findall(obj_query):
        rxn_id = clip(get_attrib(sbml_objective, "fbc:reaction"), "R_")
        try:
            objective_reaction = model.reactions.get_by_id(rxn_id)
        except KeyError:
            raise CobraSBMLError("Objective reaction '%s' not found" % rxn_id)
        try:
            coefficients[objective_reaction] = get_attrib(
                sbml_objective, "fbc:coefficient", type=number)
        except ValueError as e:
            warn(str(e))
    set_objective(model, coefficients)
    model.solver.objective.direction = obj_direction
    return model
Ejemplo n.º 30
0
def relax_lc(tmodel, metabolites_to_ignore=(), solver=None):
    """

    :param metabolites_to_ignore:
    :param in_tmodel:
    :type in_tmodel: pytfa.thermo.ThermoModel:
    :param min_objective_value:
    :return:
    """

    if solver is None:
        solver = tmodel.solver

    # Create a copy of the cobra_model on which we will perform the slack addition
    slack_model = deepcopy(tmodel)
    slack_model.solver = solver

    # Create a copy that will receive the relaxation
    relaxed_model = deepcopy(tmodel)
    relaxed_model.solver = solver

    # Do not relax if cobra_model is already optimal
    try:
        solution = tmodel.optimize()
    except SolverError as SE:
        status = tmodel.solver.status
        tmodel.logger.error(SE)
        tmodel.logger.warning('Solver status: {}'.format(status))
    # if tmodel.solver.status == OPTIMAL:
    #     raise Exception('Model is already optimal')

    # Find variables that represent standard Gibbs Energy change
    my_lc = relaxed_model.get_variables_of_type(LogConcentration)

    # Find constraints that represent negativity of Gibbs Energy change
    my_neg_dg = slack_model.get_constraints_of_type(NegativeDeltaG)

    changes = OrderedDict()
    objective = 0

    pos_slack = dict()
    neg_slack = dict()

    for this_lc in my_lc:
        if this_lc.name in metabolites_to_ignore:
            continue

        neg_slack[this_lc.name] = slack_model.add_variable(NegSlackLC,
                                                           this_lc,
                                                           lb=0,
                                                           ub=BIGM_DG)

        pos_slack[this_lc.name] = slack_model.add_variable(PosSlackLC,
                                                           this_lc,
                                                           lb=0,
                                                           ub=BIGM_DG)

        # Update the objective with the new variables
        objective += (neg_slack[this_lc.name] + pos_slack[this_lc.name])

    for this_neg_dg in my_neg_dg:

        # If there is no thermo, or relaxation forbidden, pass
        if this_neg_dg.id not in my_neg_dg:
            continue


        subs_dict = {k: slack_model.variables.get(k.name) \
                     for k in this_neg_dg.constraint.variables}

        # Create the new constraint by adding the slack variables to the
        # negative delta G constraint (from the initial cobra_model)
        new_expr = this_neg_dg.constraint.expression.subs(subs_dict)

        for this_var in this_neg_dg.constraint.variables:
            if not this_var.name in neg_slack:
                continue

            met_id = pos_slack[this_var.name].id
            the_met = slack_model.metabolites.get_by_id(met_id)
            stoich = this_neg_dg.reaction.metabolites[the_met]
            new_expr += slack_model.RT * stoich \
                        * \
            (pos_slack[this_var.name] - neg_slack[this_var.name])

        # Remove former constraint to override it
        slack_model.remove_constraint(this_neg_dg)

        # Add the new variant
        slack_model.add_constraint(NegativeDeltaG,
                                   this_neg_dg.reaction,
                                   expr=new_expr,
                                   lb=0,
                                   ub=0)

    # Change the objective to minimize slack
    set_objective(slack_model, objective)

    # Update variables and constraints references
    slack_model.repair()

    # Relax
    slack_model.objective.direction = 'min'
    relaxation = slack_model.optimize()

    # Extract the relaxation values from the solution, by type
    my_neg_slacks = slack_model.get_variables_of_type(NegSlackLC)
    my_pos_slacks = slack_model.get_variables_of_type(PosSlackLC)

    neg_slack_values = get_solution_value_for_variables(
        relaxation, my_neg_slacks)
    pos_slack_values = get_solution_value_for_variables(
        relaxation, my_pos_slacks)
    epsilon = relaxed_model.solver.configuration.tolerances.feasibility

    for this_met in relaxed_model.metabolites:
        # No thermo, or relaxation forbidden
        if this_met.id in metabolites_to_ignore or this_met.id not in my_lc:
            continue

        # Get the standard delta G variable
        the_lc = my_lc.get_by_id(this_met.id)

        # Get the relaxation
        lc_delta_lb = neg_slack_values[
            my_neg_slacks \
            .get_by_id(this_met.id).name]
        lc_delta_ub = \
            pos_slack_values[my_pos_slacks \
            .get_by_id(this_met.id).name]

        # Apply reaction delta G standard bound change
        if lc_delta_lb > 0 or lc_delta_ub > 0:

            # Store previous values
            previous_lc_lb = the_lc.variable.lb
            previous_lc_ub = the_lc.variable.ub

            # Apply change
            the_lc.variable.lb -= (lc_delta_lb + epsilon)
            the_lc.variable.ub += (lc_delta_ub + epsilon)

            # If needed, store that in a report table
            changes[this_met.id] = [
                previous_lc_lb, previous_lc_ub, lc_delta_lb, lc_delta_ub,
                the_lc.variable.lb, the_lc.variable.ub
            ]

    # Obtain relaxation
    relaxed_model.optimize()

    # Format relaxation
    relax_table = pd.DataFrame.from_dict(changes, orient='index')
    relax_table.columns = [
        'lb_in', 'ub_in', 'lb_change', 'ub_change', 'lb_out', 'ub_out'
    ]

    tmodel.logger.info('\n' + relax_table.__str__())

    relaxed_model.relaxation = relax_table

    return relaxed_model, slack_model, relax_table
Ejemplo n.º 31
0
 def objective(self, value):
     if isinstance(value, sympy.Basic):
         value = self.problem.Objective(value, sloppy=False)
     if not isinstance(value, (dict, optlang.interface.Objective)):
         value = {rxn: 1 for rxn in self.reactions.get_by_any(value)}
     set_objective(self, value, additive=False)
Ejemplo n.º 32
0
def relax_dgo(tmodel, reactions_to_ignore=(), solver=None, in_place=False):
    """
    :param t_tmodel:
    :type t_tmodel: pytfa.thermo.ThermoModel:
    :param reactions_to_ignore: Iterable of reactions that should not be relaxed
    :param solver: solver to use (e.g. 'optlang-glpk', 'optlang-cplex',
        'optlang-gurobi'
    :return: a cobra_model with relaxed bounds on standard Gibbs free energy
    """

    if solver is None:
        solver = tmodel.solver.interface

    # Create a copy of the cobra_model on which we will perform the slack addition
    slack_model = deepcopy(tmodel)
    slack_model.solver = solver
    slack_model.name = 'SlackModel ' + str(tmodel.name)
    slack_model.id = 'SlackModel_' + tmodel.id

    # Ensure the lazy updates are all done
    slack_model.repair()

    if not in_place:
        # Create a copy that will receive the relaxation
        relaxed_model = deepcopy(tmodel)
        relaxed_model.solver = solver
        relaxed_model.name = 'RelaxedModel ' + str(tmodel.name)
        relaxed_model.id = 'RelaxedModel_' + tmodel.id
        relaxed_model.repair()
    else:
        relaxed_model = slack_model

    dg_relax_config(slack_model)

    original_objective = relaxed_model.objective

    # Do not relax if cobra_model is already optimal
    # try:
    #     solution = tmodel.optimize()
    # except SolverError as SE:
    #     status = tmodel.solver.status
    #     tmodel.logger.error(SE)
    #     tmodel.logger.warning('Solver status: {}'.format(status))
    # if tmodel.solver.status == OPTIMAL:
    #     raise Exception('Model is already optimal')

    # Find variables that represent standard Gibbs Energy change
    my_dgo = relaxed_model.get_variables_of_type(DeltaGstd)

    # Find constraints that represent negativity of Gibbs Energy change
    my_neg_dg = slack_model.get_constraints_of_type(NegativeDeltaG)

    changes = OrderedDict()
    objective_symbols = []

    slack_model.logger.info('Adding slack constraints')

    slack_model.solver.update()

    for this_neg_dg in tqdm(my_neg_dg, desc='adding slacks'):

        # If there is no thermo, or relaxation forbidden, pass
        if this_neg_dg.id in reactions_to_ignore or this_neg_dg.id not in my_dgo:
            continue

        # Create the negative and positive slack variables
        # We can't queue them because they will be in an expression to declare
        # the constraint
        neg_slack = slack_model.add_variable(NegSlackVariable,
                                             this_neg_dg.reaction,
                                             lb=0,
                                             ub=BIGM_DG,
                                             queue=False)
        pos_slack = slack_model.add_variable(PosSlackVariable,
                                             this_neg_dg.reaction,
                                             lb=0,
                                             ub=BIGM_DG,
                                             queue=False)

        # Create the new constraint by adding the slack variables to the
        # negative delta G constraint (from the initial cobra_model)
        new_expr = this_neg_dg.constraint.expression
        new_expr += (pos_slack - neg_slack)

        this_reaction = this_neg_dg.reaction
        # # Remove former constraint to override it
        # slack_model.remove_constraint(slack_model._cons_dict[this_neg_dg.name])
        #
        # # Add the new variant
        # slack_model.add_constraint(NegativeDeltaG,
        #                            this_reaction,
        #                            expr=new_expr,
        #                            lb=0,
        #                            ub=0,
        #                            queue=True)

        this_neg_dg.change_expr(new_expr)

        # Update the objective with the new variables
        objective_symbols += [neg_slack, pos_slack]

    # objective = chunk_sum(objective_symbols)
    objective = symbol_sum(objective_symbols)

    # Change the objective to minimize slack
    set_objective(slack_model, objective)

    # Update variables and constraints references
    slack_model.repair()

    slack_model.logger.info('Optimizing slack model')
    # Relax
    slack_model.objective.direction = 'min'
    relaxation = slack_model.optimize()

    # Extract the relaxation values from the solution, by type
    relaxed_model.logger.info('Extracting relaxation')
    my_neg_slacks = slack_model.get_variables_of_type(NegSlackVariable)
    my_pos_slacks = slack_model.get_variables_of_type(PosSlackVariable)

    neg_slack_values = get_solution_value_for_variables(
        relaxation, my_neg_slacks)
    pos_slack_values = get_solution_value_for_variables(
        relaxation, my_pos_slacks)

    epsilon = relaxed_model.solver.configuration.tolerances.feasibility
    relaxed_model.repair()
    relaxed_model.solver.update()
    # Apply reaction delta G standard bound change
    for this_reaction in tqdm(relaxed_model.reactions, desc='applying slack'):
        # No thermo, or relaxation forbidden
        if this_reaction.id in reactions_to_ignore or this_reaction.id not in my_dgo:
            continue

        # Get the standard delta G variable
        the_dgo = my_dgo.get_by_id(this_reaction.id)

        # Get the relaxation
        dgo_delta_lb = \
            neg_slack_values[my_neg_slacks \
                .get_by_id(this_reaction.id).name]
        dgo_delta_ub = \
            pos_slack_values[my_pos_slacks \
                .get_by_id(this_reaction.id).name]

        if in_place:
            the_neg_slack = my_neg_slacks.get_by_id(this_reaction.id)
            the_neg_slack_value = slack_model.solution.raw[the_neg_slack.name]
            the_neg_slack.variable.lb = the_neg_slack_value - epsilon
            the_neg_slack.variable.ub = the_neg_slack_value + epsilon

            the_pos_slack = my_pos_slacks.get_by_id(this_reaction.id)
            the_pos_slack_value = slack_model.solution.raw[the_pos_slack.name]
            the_pos_slack.variable.lb = the_pos_slack_value - epsilon
            the_pos_slack.variable.ub = the_pos_slack_value + epsilon

        # Apply reaction delta G standard bound change
        if dgo_delta_lb > 0 or dgo_delta_ub > 0:

            # Store previous values
            previous_dgo_lb = the_dgo.variable.lb
            previous_dgo_ub = the_dgo.variable.ub

            if not in_place:
                # Apply change
                the_dgo.variable.lb -= (dgo_delta_lb + epsilon)
                the_dgo.variable.ub += (dgo_delta_ub + epsilon)

            # If needed, store that in a report table
            changes[this_reaction.id] = [
                previous_dgo_lb, previous_dgo_ub, dgo_delta_lb, dgo_delta_ub,
                the_dgo.variable.lb, the_dgo.variable.ub
            ]

    relaxed_model.repair()
    relaxed_model.logger.info('Testing relaxation')

    relaxed_model.objective = original_objective
    relaxed_model.objective.direction = 'max'

    relaxed_model.optimize()

    if len(changes) == 0:
        # The model is infeasible or something went wrong
        tmodel.logger.error('Relaxation could not complete '
                            '(no DeltaG relaxation found)')
        return relaxed_model, slack_model, None

    # Format relaxation
    relax_table = pd.DataFrame.from_dict(changes, orient='index')
    relax_table.columns = [
        'lb_in', 'ub_in', 'lb_change', 'ub_change', 'lb_out', 'ub_out'
    ]

    return relaxed_model, slack_model, relax_table
Ejemplo n.º 33
0
 def objective_coefficient(self, value):
     if self.model is None:
         raise AttributeError("cannot assign objective to a missing model")
     if self.flux_expression is not None:
         set_objective(self.model, {self: value}, additive=True)
Ejemplo n.º 34
0
def read_sbml_ec_model(
    filename: str,
    number: float = float,
    # re.Pattern does not exist in py36 so this type hint cannot be added now
    f_replace=F_REPLACE,
    set_missing_bounds: bool = False,
    hardcoded_rev_reactions: bool = True,
    **kwargs,
) -> Model:
    """Create `geckopy.Model` from SBMLDocument.

    Parameters
    ----------
    filename: str
    number: data type of stoichiometry: {float, int}
        In which data type should the stoichiometry be parsed.
    f_replace : dict of replacement functions for id replacement
    set_missing_bounds : flag to set missing bounds
    hardcoded_rev_reactions: bool
        if reversible reaction to account for proteins being consumed on both
        directions are written explicitly for in the SBML

    Returns
    -------
    cobra.core.Model

    """
    try:
        fsanitized = str(filename) if isinstance(filename, Path) else filename
        doc = _get_doc_from_filename(fsanitized)
    except IOError as e:
        raise e
    except Exception as original_error:
        raise CobraSBMLError(
            "Something went wrong reading the SBML model. Most likely the SBML"
            " model is not valid. Please check that your model is valid using "
            "the `cobra.io.sbml.validate_sbml_model` function or via the "
            "online validator at http://sbml.org/validator .\n"
            "\t`(model, errors) = validate_sbml_model(filename)`"
            "\nIf the model is valid and cannot be read please open an issue "
            f"at https://github.com/opencobra/cobrapy/issues: {original_error}"
        )

    if f_replace is None:
        f_replace = {}

    # SBML model
    model: libsbml.Model = doc.getModel()
    if model is None:
        raise CobraSBMLError("No SBML model detected in file.")
    model_fbc: libsbml.FbcModelPlugin = model.getPlugin("fbc")

    if not model_fbc:
        LOGGER.warning("Model does not contain SBML fbc package information.")
    else:
        if not model_fbc.isSetStrict():
            LOGGER.warning('Loading SBML model without fbc:strict="true"')

        # fbc-v1 (legacy)
        doc_fbc = doc.getPlugin("fbc")  # type: libsbml.FbcSBMLDocumentPlugin
        fbc_version = doc_fbc.getPackageVersion()

        if fbc_version == 1:
            LOGGER.warning("Loading SBML with fbc-v1 (models should be encoded"
                           " using fbc-v2)")
            conversion_properties = libsbml.ConversionProperties()
            conversion_properties.addOption("convert fbc v1 to fbc v2", True,
                                            "Convert FBC-v1 model to FBC-v2")
            result = doc.convert(conversion_properties)
            if result != libsbml.LIBSBML_OPERATION_SUCCESS:
                raise Exception("Conversion of SBML fbc v1 to fbc v2 failed")

    # Model
    model_id = model.getIdAttribute()
    if not libsbml.SyntaxChecker.isValidSBMLSId(model_id):
        LOGGER.error("'%s' is not a valid SBML 'SId'." % model_id)
    geckopy_model = Model(model_id,
                          hardcoded_rev_reactions=hardcoded_rev_reactions)
    geckopy_model.name = model.getName()

    # meta information
    meta = {
        "model.id": model_id,
        "level": model.getLevel(),
        "version": model.getVersion(),
        "packages": [],
    }
    # History
    creators = []
    created = None
    if model.isSetModelHistory():
        history = model.getModelHistory()  # type: libsbml.ModelHistory

        if history.isSetCreatedDate():
            created = history.getCreatedDate()

        for c in history.getListCreators():  # type: libsbml.ModelCreator
            creators.append({
                "familyName":
                c.getFamilyName() if c.isSetFamilyName() else None,
                "givenName":
                c.getGivenName() if c.isSetGivenName() else None,
                "organisation":
                c.getOrganisation() if c.isSetOrganisation() else None,
                "email":
                c.getEmail() if c.isSetEmail() else None,
            })

    meta["creators"] = creators
    meta["created"] = created
    meta["notes"] = _parse_notes_dict(doc)
    meta["annotation"] = _parse_annotations(doc)

    info = "<{}> SBML L{}V{}".format(model_id, model.getLevel(),
                                     model.getVersion())
    packages = {}
    for k in range(doc.getNumPlugins()):
        plugin = doc.getPlugin(k)  # type:libsbml.SBasePlugin
        key, value = plugin.getPackageName(), plugin.getPackageVersion()
        packages[key] = value
        info += ", {}-v{}".format(key, value)
        if key not in ["fbc", "groups", "l3v2extendedmath"]:
            LOGGER.warning(
                "SBML package '%s' not supported by cobrapy, "
                "information is not parsed",
                key,
            )
    meta["info"] = info
    meta["packages"] = packages
    geckopy_model._sbml = meta

    # notes and annotations
    geckopy_model.notes = _parse_notes_dict(model)
    geckopy_model.annotation = _parse_annotations(model)

    # Compartments
    # FIXME: update with new compartments
    compartments = {}
    for (compartment) in model.getListOfCompartments(
    ):  # noqa: E501 type: libsbml.Compartment
        cid = _check_required(compartment, compartment.getIdAttribute(), "id")
        compartments[cid] = compartment.getName()
    geckopy_model.compartments = compartments

    # Species
    metabolites = []
    # proteins that rely on the naming convention "prot_UNIPROT_ID" will be
    # catched here. Those who are annotated by groups membership will be parsed
    # after the groups are processed.
    proteins = []
    boundary_metabolites = []
    if model.getNumSpecies() == 0:
        LOGGER.warning("No metabolites in model")

    for specie in model.getListOfSpecies():  # type: libsbml.Species
        sid = _check_required(specie, specie.getIdAttribute(), "id")
        if f_replace and F_SPECIE in f_replace:
            sid = f_replace[F_SPECIE](sid)

        met = Metabolite(sid)
        met.name = specie.getName()
        met.notes = _parse_notes_dict(specie)
        met.annotation = _parse_annotations(specie)
        met.compartment = specie.getCompartment()
        initial_amount = specie.getInitialAmount()

        specie_fbc = specie.getPlugin("fbc")  # type: libsbml.FbcSpeciesPlugin
        if specie_fbc:
            met.charge = specie_fbc.getCharge()
            met.formula = specie_fbc.getChemicalFormula()
        else:
            if specie.isSetCharge():
                LOGGER.warning(
                    "Use of the species charge attribute is "
                    "discouraged, use fbc:charge "
                    "instead: %s",
                    specie,
                )
                met.charge = specie.getCharge()
            else:
                if "CHARGE" in met.notes:
                    LOGGER.warning(
                        "Use of CHARGE in the notes element is "
                        "discouraged, use fbc:charge "
                        "instead: %s",
                        specie,
                    )
                    try:
                        met.charge = int(met.notes["CHARGE"])
                    except ValueError:
                        # handle nan, na, NA, ...
                        pass

            if "FORMULA" in met.notes:
                LOGGER.warning(
                    "Use of FORMULA in the notes element is "
                    "discouraged, use fbc:chemicalFormula "
                    "instead: %s",
                    specie,
                )
                met.formula = met.notes["FORMULA"]

        # Detect boundary metabolites
        if specie.getBoundaryCondition() is True:
            boundary_metabolites.append(met)

        if not PROT_PATTERN.match(met.id):
            metabolites.append(met)
        else:
            proteins.append(Protein(met, concentration=initial_amount))

    geckopy_model.add_metabolites(metabolites)
    geckopy_model.add_proteins(proteins)

    # Add exchange reactions for boundary metabolites
    ex_reactions = []
    for met in boundary_metabolites:
        ex_rid = "EX_{}".format(met.id)
        ex_reaction = Reaction(ex_rid)
        ex_reaction.name = ex_rid
        ex_reaction.annotation = {"sbo": SBO_EXCHANGE_REACTION}
        ex_reaction.lower_bound = config.lower_bound
        ex_reaction.upper_bound = config.upper_bound
        LOGGER.warning("Adding exchange reaction %s with default bounds "
                       "for boundary metabolite: %s." %
                       (ex_reaction.id, met.id))
        # species is reactant
        ex_reaction.add_metabolites({met: -1})
        ex_reactions.append(ex_reaction)
    geckopy_model.add_reactions(ex_reactions)

    # Genes
    if model_fbc:
        for (gp) in model_fbc.getListOfGeneProducts(
        ):  # noqa: E501 type: libsbml.GeneProduct
            gid = _check_required(gp, gp.getIdAttribute(), "id")
            if f_replace and F_GENE in f_replace:
                gid = f_replace[F_GENE](gid)
            cobra_gene = Gene(gid)
            cobra_gene.name = gp.getName()
            if cobra_gene.name is None:
                cobra_gene.name = gid
            cobra_gene.annotation = _parse_annotations(gp)
            cobra_gene.notes = _parse_notes_dict(gp)

            geckopy_model.genes.append(cobra_gene)
    else:
        for (cobra_reaction) in model.getListOfReactions(
        ):  # noqa: E501 type: libsbml.Reaction
            # fallback to notes information
            notes = _parse_notes_dict(cobra_reaction)
            if "GENE ASSOCIATION" in notes:
                gpr = notes["GENE ASSOCIATION"]
            elif "GENE_ASSOCIATION" in notes:
                gpr = notes["GENE_ASSOCIATION"]
            else:
                gpr = ""

            if len(gpr) > 0:
                gpr = gpr.replace("(", ";")
                gpr = gpr.replace(")", ";")
                gpr = gpr.replace("or", ";")
                gpr = gpr.replace("and", ";")
                # Interaction of the above replacements can lead to multiple
                # ;, which results in empty gids
                gids = [t.strip() for t in gpr.split(";")]
                gids = set(gids).difference({""})

                # create missing genes
                for gid in gids:
                    if f_replace and F_GENE in f_replace:
                        gid = f_replace[F_GENE](gid)

                    if gid not in geckopy_model.genes:
                        cobra_gene = Gene(gid)
                        cobra_gene.name = gid
                        geckopy_model.genes.append(cobra_gene)

    # GPR rules
    def process_association(ass):
        """Recursively convert gpr association to a gpr string.

        Defined as inline functions to not pass the replacement dict around.
        """
        if ass.isFbcOr():
            return " ".join([
                "(",
                " or ".join(
                    process_association(c)
                    for c in ass.getListOfAssociations()),
                ")",
            ])
        elif ass.isFbcAnd():
            return " ".join([
                "(",
                " and ".join(
                    process_association(c)
                    for c in ass.getListOfAssociations()),
                ")",
            ])
        elif ass.isGeneProductRef():
            gid = ass.getGeneProduct()
            if f_replace and F_GENE in f_replace:
                return f_replace[F_GENE](gid)
            else:
                return gid

    # Reactions
    missing_bounds = False
    reactions = []
    if model.getNumReactions() == 0:
        LOGGER.warning("No reactions in model")

    for reaction in model.getListOfReactions():  # type: libsbml.Reaction
        rid = _check_required(reaction, reaction.getIdAttribute(), "id")
        # proteins are parsed based on Species, prot exchanges are ignored
        if PROT_EX_PATTERN.search(rid):
            continue
        if f_replace and F_REACTION in f_replace:
            rid = f_replace[F_REACTION](rid)
        cobra_reaction = Reaction(rid)
        cobra_reaction.name = reaction.getName()
        cobra_reaction.annotation = _parse_annotations(reaction)
        cobra_reaction.notes = _parse_notes_dict(reaction)

        # set bounds
        p_ub, p_lb = None, None
        r_fbc = reaction.getPlugin("fbc")  # type: libsbml.FbcReactionPlugin
        if r_fbc:
            # bounds in fbc
            lb_id = r_fbc.getLowerFluxBound()
            if lb_id:
                p_lb = model.getParameter(lb_id)  # type: libsbml.Parameter
                if p_lb and p_lb.getConstant() and (p_lb.getValue()
                                                    is not None):
                    cobra_reaction.lower_bound = p_lb.getValue()
                else:
                    raise CobraSBMLError("No constant bound '%s' for "
                                         "reaction: %s" % (p_lb, reaction))

            ub_id = r_fbc.getUpperFluxBound()
            if ub_id:
                p_ub = model.getParameter(ub_id)  # type: libsbml.Parameter
                if p_ub and p_ub.getConstant() and (p_ub.getValue()
                                                    is not None):
                    cobra_reaction.upper_bound = p_ub.getValue()
                else:
                    raise CobraSBMLError("No constant bound '%s' for "
                                         "reaction: %s" % (p_ub, reaction))

        elif reaction.isSetKineticLaw():
            # some legacy models encode bounds in kinetic laws
            klaw = reaction.getKineticLaw()  # type: libsbml.KineticLaw
            p_lb = klaw.getParameter(
                "LOWER_BOUND")  # noqa: E501 type: libsbml.LocalParameter
            if p_lb:
                cobra_reaction.lower_bound = p_lb.getValue()
            p_ub = klaw.getParameter(
                "UPPER_BOUND")  # noqa: E501 type: libsbml.LocalParameter
            if p_ub:
                cobra_reaction.upper_bound = p_ub.getValue()

            if p_ub is not None or p_lb is not None:
                LOGGER.warning(
                    "Encoding LOWER_BOUND and UPPER_BOUND in "
                    "KineticLaw is discouraged, "
                    "use fbc:fluxBounds instead: %s",
                    reaction,
                )

        if p_lb is None:
            missing_bounds = True
            lower_bound = config.lower_bound
            cobra_reaction.lower_bound = lower_bound
            LOGGER.warning(
                "Missing lower flux bound set to '%s' for "
                " reaction: '%s'",
                lower_bound,
                reaction,
            )

        if p_ub is None:
            missing_bounds = True
            upper_bound = config.upper_bound
            cobra_reaction.upper_bound = upper_bound
            LOGGER.warning(
                "Missing upper flux bound set to '%s' for "
                " reaction: '%s'",
                upper_bound,
                reaction,
            )

        # add reaction
        reactions.append(cobra_reaction)

        # parse equation
        stoichiometry = defaultdict(lambda: 0)
        for (sref) in reaction.getListOfReactants(
        ):  # noqa: E501 type: libsbml.SpeciesReference
            sid = _check_required(sref, sref.getSpecies(), "species")

            if f_replace and F_SPECIE in f_replace:
                sid = f_replace[F_SPECIE](sid)
            stoichiometry[sid] -= number(
                _check_required(sref, sref.getStoichiometry(),
                                "stoichiometry"))

        for (sref) in reaction.getListOfProducts(
        ):  # noqa: E501 type: libsbml.SpeciesReference
            sid = _check_required(sref, sref.getSpecies(), "species")

            if f_replace and F_SPECIE in f_replace:
                sid = f_replace[F_SPECIE](sid)
            stoichiometry[sid] += number(
                _check_required(sref, sref.getStoichiometry(),
                                "stoichiometry"))

        # convert to metabolite objects
        object_stoichiometry = {}
        for met_id in stoichiometry:
            target_set = (geckopy_model.proteins
                          if met_id in geckopy_model.proteins else
                          geckopy_model.metabolites)
            metabolite = target_set.get_by_id(met_id)
            object_stoichiometry[metabolite] = stoichiometry[met_id]
        cobra_reaction.add_metabolites(object_stoichiometry)

        # GPR
        if r_fbc:
            gpr = ""
            gpa = (r_fbc.getGeneProductAssociation()
                   )  # noqa: E501 type: libsbml.GeneProductAssociation
            if gpa is not None:
                association = (gpa.getAssociation()
                               )  # noqa: E501 type: libsbml.FbcAssociation
                gpr = process_association(association)
        else:
            # fallback to notes information
            notes = cobra_reaction.notes
            if "GENE ASSOCIATION" in notes:
                gpr = notes["GENE ASSOCIATION"]
            elif "GENE_ASSOCIATION" in notes:
                gpr = notes["GENE_ASSOCIATION"]
            else:
                gpr = ""

            if len(gpr) > 0:
                LOGGER.warning(
                    "Use of GENE ASSOCIATION or GENE_ASSOCIATION "
                    "in the notes element is discouraged, use "
                    "fbc:gpr instead: %s",
                    reaction,
                )
                if f_replace and F_GENE in f_replace:
                    gpr = " ".join(f_replace[F_GENE](t)
                                   for t in gpr.split(" "))

        # remove outside parenthesis, if any
        if gpr.startswith("(") and gpr.endswith(")"):
            try:
                parse_gpr(gpr[1:-1].strip())
                gpr = gpr[1:-1].strip()
            except (SyntaxError, TypeError) as e:
                LOGGER.warning(
                    f"Removing parenthesis from gpr {gpr} leads to "
                    f"an error, so keeping parenthesis, error: {e}", )

        cobra_reaction.gene_reaction_rule = gpr

    geckopy_model.add_reactions(reactions)

    # Objective
    obj_direction = "max"
    coefficients = {}
    if model_fbc:
        obj_list = (model_fbc.getListOfObjectives()
                    )  # noqa: E501 type: libsbml.ListOfObjectives
        if obj_list is None:
            LOGGER.warning("listOfObjectives element not found")
        elif obj_list.size() == 0:
            LOGGER.warning("No objective in listOfObjectives")
        elif not obj_list.getActiveObjective():
            LOGGER.warning("No active objective in listOfObjectives")
        else:
            obj_id = obj_list.getActiveObjective()
            obj = model_fbc.getObjective(obj_id)  # type: libsbml.Objective
            obj_direction = LONG_SHORT_DIRECTION[obj.getType()]

            for (flux_obj) in (obj.getListOfFluxObjectives()
                               ):  # noqa: E501 type: libsbml.FluxObjective
                rid = flux_obj.getReaction()
                if f_replace and F_REACTION in f_replace:
                    rid = f_replace[F_REACTION](rid)
                try:
                    objective_reaction = geckopy_model.reactions.get_by_id(rid)
                except KeyError:
                    raise CobraSBMLError("Objective reaction '%s' "
                                         "not found" % rid)
                try:
                    coefficients[objective_reaction] = number(
                        flux_obj.getCoefficient())
                except ValueError as e:
                    LOGGER.warning(str(e))
    else:
        # some legacy models encode objective coefficients in kinetic laws
        for reaction in model.getListOfReactions():  # type: libsbml.Reaction
            if reaction.isSetKineticLaw():
                klaw = reaction.getKineticLaw()  # type: libsbml.KineticLaw
                p_oc = klaw.getParameter(
                    "OBJECTIVE_COEFFICIENT")  # type: libsbml.LocalParameter
                if p_oc:
                    rid = _check_required(reaction, reaction.getIdAttribute(),
                                          "id")
                    if f_replace and F_REACTION in f_replace:
                        rid = f_replace[F_REACTION](rid)
                    try:
                        objective_reaction = geckopy_model.reactions.get_by_id(
                            rid)
                    except KeyError:
                        raise CobraSBMLError(
                            "Objective reaction '%s' "
                            "not found", rid)
                    try:
                        coefficients[objective_reaction] = number(
                            p_oc.getValue())
                    except ValueError as e:
                        LOGGER.warning(str(e))

                    LOGGER.warning(
                        "Encoding OBJECTIVE_COEFFICIENT in "
                        "KineticLaw is discouraged, "
                        "use fbc:fluxObjective "
                        "instead: %s",
                        reaction,
                    )

    if len(coefficients) == 0:
        LOGGER.error("No objective coefficients in model. Unclear what should "
                     "be optimized")
    set_objective(geckopy_model, coefficients)
    geckopy_model.solver.objective.direction = obj_direction

    # parse groups
    model_groups = model.getPlugin("groups")  # type: libsbml.GroupsModelPlugin
    groups = []
    if model_groups:
        # calculate hashmaps to lookup objects in O(1)
        sid_map = {}
        metaid_map = {}
        for obj_list in [
                model.getListOfCompartments(),
                model.getListOfSpecies(),
                model.getListOfReactions(),
                model_groups.getListOfGroups(),
        ]:

            for sbase in obj_list:  # type: libsbml.SBase
                if sbase.isSetId():
                    sid_map[sbase.getIdAttribute()] = sbase
                if sbase.isSetMetaId():
                    metaid_map[sbase.getMetaId()] = sbase

        # create groups
        for group in model_groups.getListOfGroups():  # type: libsbml.Group
            gid = _check_required(group, group.getIdAttribute(), "id")
            if f_replace and F_GROUP in f_replace:
                gid = f_replace[F_GROUP](gid)
            cobra_group = Group(gid)
            cobra_group.name = group.getName()
            if group.isSetKind():
                cobra_group.kind = group.getKindAsString()
            cobra_group.annotation = _parse_annotations(group)
            cobra_group.notes = _parse_notes_dict(group)

            cobra_members = []
            for member in group.getListOfMembers():  # type: libsbml.Member
                if member.isSetIdRef():
                    obj = sid_map[member.getIdRef()]
                elif member.isSetMetaIdRef():
                    obj = metaid_map[member.getMetaIdRef()]

                typecode = obj.getTypeCode()
                obj_id = _check_required(obj, obj.getIdAttribute(), "id")

                # id replacements
                cobra_member = None
                if typecode == libsbml.SBML_SPECIES:
                    if f_replace and F_SPECIE in f_replace:
                        obj_id = f_replace[F_SPECIE](obj_id)
                    try:
                        cobra_member = geckopy_model.metabolites.get_by_id(
                            obj_id)
                    except KeyError:
                        cobra_member = geckopy_model.proteins.get_by_id(obj_id)
                elif typecode == libsbml.SBML_REACTION:
                    if f_replace and F_REACTION in f_replace:
                        obj_id = f_replace[F_REACTION](obj_id)
                    cobra_member = geckopy_model.reactions.get_by_id(obj_id)
                elif typecode == libsbml.SBML_FBC_GENEPRODUCT:
                    if f_replace and F_GENE in f_replace:
                        obj_id = f_replace[F_GENE](obj_id)
                    cobra_member = geckopy_model.genes.get_by_id(obj_id)
                else:
                    LOGGER.warning("Member %s could not be added to group %s."
                                   "unsupported type code: "
                                   "%s" % (member, group, typecode))

                if cobra_member:
                    cobra_members.append(cobra_member)

            cobra_group.add_members(cobra_members)
            groups.append(cobra_group)
    else:
        # parse deprecated subsystems on reactions
        groups_dict = {}
        for cobra_reaction in geckopy_model.reactions:
            if "SUBSYSTEM" in cobra_reaction.notes:
                g_name = cobra_reaction.notes["SUBSYSTEM"]
                if g_name in groups_dict:
                    groups_dict[g_name].append(cobra_reaction)
                else:
                    groups_dict[g_name] = [cobra_reaction]

        for gid, cobra_members in groups_dict.items():
            if f_replace and F_GROUP in f_replace:
                gid = f_replace[F_GROUP](gid)
            cobra_group = Group(gid, name=gid, kind="collection")
            cobra_group.add_members(cobra_members)
            groups.append(cobra_group)

    geckopy_model.add_groups(groups)

    # now add everything under group Proteins to model.proteins if it was not
    # already added based on naming conventions
    if geckopy_model.groups.query("Protein"):
        g_proteins = geckopy_model.groups.Protein.members.copy()
        g_proteins = {
            prot: {reac: reac.metabolites[prot]
                   for reac in prot.reactions}
            for prot in g_proteins if prot not in geckopy_model.proteins
        }

        if g_proteins:
            geckopy_model.remove_metabolites(g_proteins.keys())
            geckopy_model.add_proteins([Protein(prot) for prot in g_proteins])
            for prot, reactions in g_proteins.items():
                for reac, stoich in reactions.items():
                    # reverse the (negative) stoichiometry coefficient to kcat
                    # we expect that Proteins that are identified only by their
                    # group are respecting the specification and do form part
                    # of reactions
                    # TODO: provide options to tune this behvior
                    reac.add_protein(prot.id, -1 / (stoich * 3600))

    # general hint for missing flux bounds
    if missing_bounds:
        LOGGER.warning(
            "Missing flux bounds on reactions set to default bounds."
            "As best practise and to avoid confusion flux bounds "
            "should be set explicitly on all reactions.")

    return geckopy_model