12. Using COBRApy with MASSpy

This notebook example demonstrates how to convert COBRApy objects into their equivalent MASSpy objects, and highlights some of the differences between them.

[1]:
import cobra.test

from mass import MassMetabolite, MassModel, MassReaction

12.1. Converting COBRA to MASS

Converting COBRApy objects into their MASSpy equivalents is a simple process. It only requires the user to instantiate the MASSpy object using the COBRApy object.

[2]:
# Get some COBRA objects
cobra_model = cobra.test.create_test_model("textbook")
cobra_metabolite = cobra_model.metabolites.get_by_id("atp_c")
cobra_reaction = cobra_model.reactions.get_by_id("PGI")
Set parameter Username

12.1.1. Metabolite to MassMetabolite

To convert a cobra.Metabolite to a mass.MassMetabolite:

[3]:
mass_metabolite = MassMetabolite(cobra_metabolite)
mass_metabolite
[3]:
MassMetabolite identifier atp_c
Name ATP
Memory address 0x07febc9889f70
Formula C10H12N5O13P3
Compartment c
Initial Condition None
In 0 reaction(s)

Note that converted metabolites do not retain any references to the previously associated cobra.Reaction or cobra.Model.

[4]:
for metabolite in [cobra_metabolite, mass_metabolite]:
    print("Number of Reactions: {0}; Model: {1}".format(len(metabolite.reactions), metabolite.model))
Number of Reactions: 13; Model: e_coli_core
Number of Reactions: 0; Model: None

However, all attributes that the mass.MassMetabolite object inherits from the cobra.Metabolite object are preserved:

[5]:
for attr in ["id", "name", "formula", "charge", "compartment"]:
    print("Identical '{0}': {1}".format(
        attr, getattr(cobra_metabolite, attr) == getattr(mass_metabolite, attr)))
Identical 'id': True
Identical 'name': True
Identical 'formula': True
Identical 'charge': True
Identical 'compartment': True

12.1.2. Reaction to MassReaction

To convert a cobra.Reaction to a mass.MassReaction:

[6]:
mass_reaction = MassReaction(cobra_reaction)
mass_reaction
[6]:
Reaction identifier PGI
Name glucose-6-phosphate isomerase
Memory address 0x07febc9889fd0
Subsystem
Kinetic Reversibility True
Stoichiometry

g6p_c <=> f6p_c

D-Glucose 6-phosphate <=> D-Fructose 6-phosphate

GPRb4025
Bounds(-1000.0, 1000.0)

Upon conversion of a reaction, all associated cobra.Metabolite objects are converted to mass.MassMetabolite objects.

[7]:
for metabolite in mass_reaction.metabolites:
    print(metabolite, type(metabolite))
g6p_c <class 'mass.core.mass_metabolite.MassMetabolite'>
f6p_c <class 'mass.core.mass_metabolite.MassMetabolite'>

If there are genes present, they are copied from one reaction to another in order to create a new cobra.Gene object for the MassReaction.

[8]:
print(cobra_reaction.genes)
print(mass_reaction.genes)
frozenset({<Gene b4025 at 0x7febb95c2b80>})
frozenset({<Gene b4025 at 0x7feba0e93eb0>})

All other references to COBRApy objects are removed.

[9]:
print(cobra_reaction.model)
print(mass_reaction.model)
e_coli_core
None

All attributes that the mass.MassReaction object inherits from the cobra.Reaction object are preserved upon conversion.

[10]:
for attr in ["id", "name", "subsystem", "bounds", "compartments", "gene_reaction_rule"]:
    print("Identical '{0}': {1}".format(
        attr, getattr(cobra_reaction, attr) == getattr(mass_reaction, attr)))
Identical 'id': True
Identical 'name': True
Identical 'subsystem': True
Identical 'bounds': True
Identical 'compartments': True
Identical 'gene_reaction_rule': True

12.1.3. Model to MassModel

To convert a cobra.Model to a mass.MassModel:

[11]:
mass_model = MassModel(cobra_model)
mass_model
[11]:
Namee_coli_core
Memory address0x07feba0e93f70
Stoichiometric Matrix 72x95
Matrix Rank 67
Number of metabolites 72
Initial conditions defined 0/72
Number of reactions 95
Number of genes 137
Number of enzyme modules 0
Number of groups 0
Objective expression 1.0*Biomass_Ecoli_core - 1.0*Biomass_Ecoli_core_reverse_2cdba
Compartments cytosol, extracellular

During conversion, the original cobra.Model remains untouched, while a new mass.MassModel is created using the equivalent mass objects. All references to the original cobra.Model are updated with references to the newly created mass.MassModel.

[12]:
print("All MassMetabolites: {0}".format(
    all([isinstance(met, MassMetabolite)
         for met in mass_model.metabolites])))
print("All MassReactions: {0}".format(
    all([isinstance(rxn, MassReaction)
         for rxn in mass_model.reactions])))
All MassMetabolites: True
All MassReactions: True

12.2. Differences between COBRA and MASS

Although there are several similarities between COBRApy and MASSpy, there are some key differences in behavior that are worth highlighting.

12.2.1. COBRA vs. MASS reactions

There are some key differences between cobra.Reaction and mass.MassReaction objects. They are summarized below:

12.2.1.1. reversible vs. reversibility attributes

One key difference observed is how a reaction direction is determined. A cobra.Reaction utilizes the lower and upper bound values to determine the reversibility attribute.

[13]:
print(cobra_reaction.reaction)
print(cobra_reaction.bounds)
print(cobra_reaction.reversibility)
g6p_c <=> f6p_c
(-1000.0, 1000.0)
True

Changing the reaction bounds affects the direction a reaction can proceed:

[14]:
for header, bounds in zip(["Both Directions", "Forward Direction", "Reverse Direction"],
                          [(-1000, 1000), (0, 1000), (-1000, 0)]):

    print("\n".join((header, "-" * len(header))))
    cobra_reaction.bounds = bounds
    print(cobra_reaction.reaction)
    print(cobra_reaction.bounds)
    print("Reversibility: {0}\n".format(cobra_reaction.reversibility))
Both Directions
---------------
g6p_c <=> f6p_c
(-1000, 1000)
Reversibility: True

Forward Direction
-----------------
g6p_c --> f6p_c
(0, 1000)
Reversibility: False

Reverse Direction
-----------------
g6p_c <-- f6p_c
(-1000, 0)
Reversibility: False

Although MassReaction objects still have the reversibility attribute based on reaction bounds, the reaction rate equation is based on the reversible attribute. Additionally, the displayed reaction arrow for a reaction string now depends on the reversible attribute, rather than the reversibility attribute.

Therefore, even if the flux is constrained to proceed in one direction by the bounds, the kinetic rate expression still accounts for a reverse rate.

[15]:
for header, bounds in zip(["Forward Direction (Flux)", "Reverse Direction (Flux)", "Both Directions (Flux)"],
                          [(0, 1000), (-1000, 0), (-1000, 1000)]):

    print("\n".join((header, "-" * len(header))))
    mass_reaction.bounds = bounds
    print(mass_reaction.reaction)
    print(mass_reaction.bounds)
    print("Reversibility: {0}".format(mass_reaction.reversibility))
    print("Reversible (Kinetic): {0}".format(mass_reaction.reversible))
    print("Rate: {0}\n".format(mass_reaction.rate))
Forward Direction (Flux)
------------------------
g6p_c <=> f6p_c
(0, 1000)
Reversibility: False
Reversible (Kinetic): True
Rate: kf_PGI*(g6p_c(t) - f6p_c(t)/Keq_PGI)

Reverse Direction (Flux)
------------------------
g6p_c <=> f6p_c
(-1000, 0)
Reversibility: False
Reversible (Kinetic): True
Rate: kf_PGI*(g6p_c(t) - f6p_c(t)/Keq_PGI)

Both Directions (Flux)
----------------------
g6p_c <=> f6p_c
(-1000, 1000)
Reversibility: True
Reversible (Kinetic): True
Rate: kf_PGI*(g6p_c(t) - f6p_c(t)/Keq_PGI)

Changing the reversible attribute affects the kinetic rate expression for the reaction, but it does not affect the reaction bounds.

[16]:
for header, reversible in zip(["Both Directions (Kinetics)", "Forward Direction (Kinetics)"], [True, False]):
    print("\n".join((header, "-" * len(header))))
    mass_reaction.reversible = reversible
    print(mass_reaction.reaction)
    print(mass_reaction.bounds)
    print("Reversibility: {0}".format(mass_reaction.reversibility))
    print("Reversible (Kinetic): {0}".format(mass_reaction.reversible))
    print("Rate: {0}\n".format(mass_reaction.rate))
Both Directions (Kinetics)
--------------------------
g6p_c <=> f6p_c
(-1000, 1000)
Reversibility: True
Reversible (Kinetic): True
Rate: kf_PGI*(g6p_c(t) - f6p_c(t)/Keq_PGI)

Forward Direction (Kinetics)
----------------------------
g6p_c --> f6p_c
(-1000, 1000)
Reversibility: True
Reversible (Kinetic): False
Rate: kf_PGI*g6p_c(t)

To obtain the reaction in the reverse direction instead of the forward direction, the MassReaction.reverse_stoichiometry() method can be used. Setting inplace=False produces a new reaction, while setting inplace=True modifies the existing reaction. Setting reverse_bounds=True switches the lower and upper bound values.

[17]:
mass_reaction_rev = mass_reaction.reverse_stoichiometry(inplace=False)
print(mass_reaction_rev.reaction)
print(mass_reaction_rev.bounds)
print("Reversibility: {0}".format(mass_reaction_rev.reversibility))
print("Reversible (Kinetic): {0}".format(mass_reaction_rev.reversible))
print("Rate: {0}\n".format(mass_reaction_rev.rate))
f6p_c --> g6p_c
(-1000, 1000)
Reversibility: True
Reversible (Kinetic): False
Rate: kf_PGI*f6p_c(t)

12.2.1.2. flux vs. steady_state_flux attributes

Another difference observed between cobra.Reaction and mass.MassReaction is how flux values are stored. When a model is optimized for FBA, the flux attribute of the reaction reflects the solution directly produced by the solver.

[18]:
cobra_model = cobra.test.create_test_model("textbook")
cobra_model.optimize()
cobra_reaction = cobra_model.reactions.get_by_id("PGI")
cobra_reaction.flux
[18]:
4.860861146496812

MassModel objects retain their ability to be optimized for FBA. Consequently, the ability to retrieve a solution for a reaction using the flux attribute is also retained.

[19]:
cobra_model = cobra.test.create_test_model("textbook")
mass_model = MassModel(cobra_model)
mass_model.optimize()
mass_reaction = mass_model.reactions.get_by_id("PGI")
mass_reaction.flux
[19]:
4.860861146496812

The value of the flux attribute is not the same as the steady_state_flux attribute, which is used in various mass methods:

[20]:
print(mass_reaction.steady_state_flux)
None

To set steady_state_flux attributes for all reactions based on the optimization solutions, the MassModel.set_steady_state_fluxes_from_solver() method is used.

[21]:
mass_model.set_steady_state_fluxes_from_solver()
# Display for first 10 reactions
for reaction in mass_model.reactions[:10]:
    print(reaction.id, reaction.steady_state_flux)
ACALD 0.0
ACALDt 0.0
ACKr 0.0
ACONTa 6.007249575350327
ACONTb 6.007249575350327
ACt2r 0.0
ADK1 0.0
AKGDH 5.064375661482088
AKGt2r 0.0
ALCD2x 0.0