Skip to content

Commit 4f7219e

Browse files
committed
Merge branch 'development'
introducing object-oriented approach from development branch.
2 parents 8fe47f5 + e48060a commit 4f7219e

38 files changed

+665150
-314
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Medusa - Analysis of ensembles of metabolic network reconstructions
22
===================================================================
33

4+
|Build Status| |PyPI|
5+
46
Medusa is a tool for constraint-based reconstruction and analysis (COBRA) of ensembles. It builds on the cobrapy package (https://github.com/opencobra/cobrapy) by extending most single-model functionality to efficient ensemble-scale analysis. Additionally, Medusa provides novel functions for the analysis of ensembles.
57

68
Medusa is developed openly. We are currently porting code from previous/in-progress projects to generalize beyond the specific use cases that project code was originally developed for. Development plans can be found in the root of the repository in "version_release_plan.txt".
@@ -45,3 +47,7 @@ Feel free to post issues via github or contact us directly via email with any qu
4547
Authors:
4648
Greg Medlock (gmedlo [at] gmail [dot] com)
4749

50+
.. |Build Status| image:: https://api.travis-ci.org/gregmedlock/Medusa.svg?branch=master
51+
:target: https://travis-ci.org/gregmedlock/Medusa/
52+
.. |PyPI| image:: https://badge.fury.io/py/medusa-cobra.svg
53+
:target: https://pypi.python.org/pypi/medusa-cobra

medusa/core/ensemble.py

Lines changed: 137 additions & 285 deletions
Large diffs are not rendered by default.

medusa/core/feature.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
from __future__ import absolute_import
3+
4+
from cobra.core.object import Object
5+
6+
class Feature(Object):
7+
"""
8+
Feature describing a network component that varies across an ensemble.
9+
10+
Parameters
11+
----------
12+
identifier : string
13+
The identifier to associate with the feature. Convention is to append the
14+
component_attribute to the base_component's id.
15+
16+
ensemble : medusa.core.ensemble.Ensemble object
17+
The ensemble that the feature is associated with.
18+
19+
base_component : cobra.core.reaction.Reaction
20+
Reference to the Reaction object that the feature describes.
21+
22+
component_attribute : string
23+
string indicating the attribute of base_component that the feature
24+
describes the modification of (e.g. "lb", "ub")
25+
26+
states : dictionary of string:component_attribute value
27+
dictionary of model ids mapping to the value of the Feature's
28+
component_attribute (value type depends on component_attribute type,
29+
e.g. float for "lb", string for "_gene_reaction_rule")
30+
31+
Attributes
32+
----------
33+
34+
"""
35+
36+
def __init__(self, identifier=None, name=None,ensemble=None,\
37+
base_component=None, component_attribute=None, states=None):
38+
Object.__init__(self,identifier,name)
39+
self.ensemble = ensemble
40+
self.base_component = base_component
41+
self.component_attribute = component_attribute
42+
self.states = states
43+
44+
45+
def get_model_state(self,member_id):
46+
"""Get the state of the feature for a particular member
47+
"""
48+
return self.states[member_id]

medusa/core/member.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
from __future__ import absolute_import
3+
4+
from cobra.core.object import Object
5+
6+
class Member(Object):
7+
"""
8+
Object representing an individual member (i.e. model) in an ensemble
9+
10+
Parameters
11+
----------
12+
identifier : string
13+
The identifier to associate with the member.
14+
15+
ensemble : medusa.core.ensemble.Ensemble object
16+
The ensemble that the member belongs to.
17+
18+
states : dictionary of medusa.core.feature.Feature:component_attribute value
19+
dictionary of Features mapping to the value of the Feature's
20+
component_attribute (value type depends on component_attribute type,
21+
e.g. float for "lb", string for "_gene_reaction_rule") for the member.
22+
23+
Attributes
24+
----------
25+
26+
"""
27+
28+
def __init__(self,ensemble=None, identifier=None, name=None, states=None):
29+
Object.__init__(self,identifier,name)
30+
self.ensemble = ensemble
31+
self.states = states

medusa/flux_analysis/deletion.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
3+
def ensemble_single_reaction_deletion(ensemble,optimal_only=True,num_models=[]):
4+
'''
5+
IN PROGRESS, not functional. Need to refactor for features/states.
6+
7+
Performs single reaction deletions for num_models in the ensemble. Reports
8+
absolute objective value, and can be modified to return only optimal solutions.
9+
10+
Currently does not return the solver status for all members of an ensemble,
11+
so infeasible and 0 flux values can't be discriminated unless optimal_only=True
12+
is passed (in which case feasible solutions with flux through the objective
13+
of 0 are returned, but infeasible solutions are not)
14+
'''
15+
if not num_models:
16+
# if not specified, use all models
17+
num_models = len(ensemble.reaction_diffs.keys())
18+
19+
# for each model, perform all the deletions, then advance to the next model.
20+
for model in random.sample(list(ensemble.reaction_diffs.keys()),num_models):
21+
# set the correct bounds for the model
22+
ensemble._apply_diffs(model)
23+
# perform single reaction deletion for all reactions in the model
24+
# TODO: make exception for biomass and the demand reaction.
25+
print('performing deletions for ' + model)
26+
model_deletion_results = cobra.single_reaction_deletion(self.base_model,self.base_model.reactions)
27+
28+
# if optimal_only, filter the output dataframe for each model to exclude infeasibles,
29+
# then append to a master dataframe

medusa/flux_analysis/flux_balance.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def optimize_ensemble(ensemble,return_flux=None,num_models=None,specific_models=
3737
'''
3838

3939
if not num_models:
40-
num_models = len(ensemble.reaction_diffs.keys())
40+
num_models = len(ensemble.members)
4141

4242
if isinstance(return_flux,str):
4343
return_flux = [return_flux]
@@ -47,14 +47,10 @@ def optimize_ensemble(ensemble,return_flux=None,num_models=None,specific_models=
4747
if specific_models:
4848
model_list = specific_models
4949
else:
50-
model_list = sample(list(ensemble.reaction_diffs.keys()),num_models)
50+
model_list = sample(ensemble.members,num_models)
5151
with ensemble.base_model:
5252
for model in model_list:
53-
diffs = ensemble.reaction_diffs[model]
54-
for reaction in diffs.keys():
55-
rxn = ensemble.base_model.reactions.get_by_id(reaction)
56-
rxn.lower_bound = ensemble.reaction_diffs[model][reaction]['lb']
57-
rxn.upper_bound = ensemble.reaction_diffs[model][reaction]['ub']
53+
ensemble.set_state(model)
5854
ensemble.base_model.optimize(**kwargs)
5955
flux_dict[model] = {rxn.id:rxn.flux for rxn in ensemble.base_model.reactions}
6056
if return_flux:

medusa/flux_analysis/variability.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def ensemble_fva(ensemble, reaction_list, num_models=[],specific_models=None,
5353
'''
5454
if not num_models:
5555
# if not specified, use all models
56-
num_models = len(ensemble.reaction_diffs.keys())
56+
num_models = len(ensemble.members)
5757

5858
if isinstance(reaction_list,str):
5959
reaction_list = [reaction_list]
@@ -65,14 +65,11 @@ def ensemble_fva(ensemble, reaction_list, num_models=[],specific_models=None,
6565
if specific_models:
6666
model_list = specific_models
6767
else:
68-
model_list = sample(list(ensemble.reaction_diffs.keys()),num_models)
68+
model_list = sample(ensemble.members,num_models)
69+
model_list = [model.id for model in model_list]
6970
with ensemble.base_model:
7071
for model in model_list:
71-
diffs = ensemble.reaction_diffs[model]
72-
for reaction in diffs.keys():
73-
rxn = ensemble.base_model.reactions.get_by_id(reaction)
74-
rxn.lower_bound = ensemble.reaction_diffs[model][reaction]['lb']
75-
rxn.upper_bound = ensemble.reaction_diffs[model][reaction]['ub']
72+
ensemble.set_state(model)
7673
fva_result = flux_variability_analysis(
7774
ensemble.base_model,reaction_list=reaction_list,
7875
fraction_of_optimum=fraction_of_optimum,

medusa/quality/mass_balance.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
3+
def leak_test(ensemble,metabolites_to_test=[],\
4+
exchange_prefix='EX_',verbose=False,num_models=[],**kwargs):
5+
'''
6+
Checks for leaky metabolites in every member of the ensemble by opening
7+
and optimizing a demand reaction while all exchange reactions are closed.
8+
9+
By default, checks for leaks for every metabolite for all models.
10+
'''
11+
12+
if not num_models:
13+
# if the number of models wasn't specified, test all
14+
num_models = len(self.reaction_diffs.keys())
15+
16+
if not metabolites_to_test:
17+
metabolites_to_test = [met for met in self.base_model.metabolites]
18+
19+
old_objective = ensemble.base_model.objective
20+
dm_rxns = []
21+
for met in metabolites_to_test:
22+
rxn = cobra.Reaction(id='leak_DM_' + met.id)
23+
rxn.lower_bound = 0.0
24+
rxn.upper_bound = 0.0
25+
rxn.add_metabolites({met:-1})
26+
dm_rxns.append(rxn)
27+
ensemble.base_model.add_reactions(dm_rxns)
28+
ensemble.base_model.repair()
29+
30+
leaks = {}
31+
32+
for rxn in dm_rxns:
33+
rxn.upper_bound = 1000.0
34+
ensemble.base_model.objective = ensemble.base_model.reactions.get_by_id(rxn.id)
35+
36+
if verbose:
37+
print('checking leak for ' + rxn.id)
38+
solutions = ensemble.optimize_ensemble(return_flux=[rxn.id],num_models=num_models,**kwargs)
39+
leaks[rxn.id.split('_DM_')[1]] = {}
40+
for model in solutions.keys():
41+
leaks[rxn.id.split('_DM_')[1]][model] = solutions[model][rxn.id] > 0.0001
42+
#rxn.objective_coefficient = 0.0
43+
rxn.upper_bound = 0.0
44+
45+
# remove the demand reactions and restore the original objective
46+
ensemble.base_model.remove_reactions(dm_rxns,remove_orphans=True)
47+
ensemble.base_model.repair()
48+
ensemble.base_model.objective = old_objective
49+
50+
return leaks
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
from __future__ import absolute_import
3+
4+
5+
6+
7+
8+
9+
10+
# functions for degrading networks to construct ensembles
11+
12+
def degrade_reactions(base_model,num_reactions,num_models=10):
13+
"""
14+
Removes reactions from an existing COBRA model to generate an ensemble.
15+
16+
Parameters
17+
----------
18+
base_model: cobra.Model
19+
Model from which reactions will be removed to create an ensemble.
20+
num_reactions: int
21+
The number of reactions to remove to generate each ensemble member.
22+
Must be smaller than the total number of reactions in the model
23+
num_models: int
24+
The number of models to generate by randomly removing num_reactions from
25+
the base_model. Reactions are removed with replacement.
26+
27+
Returns
28+
-------
29+
Medusa.core.ensemble
30+
An ensemble
31+
"""
32+
filler=1

medusa/test/__init__.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import absolute_import
2+
3+
import cobra
4+
import json
5+
6+
from medusa.core.ensemble import Ensemble
7+
8+
9+
from os.path import abspath, dirname, join
10+
11+
medusa_directory = abspath(join(dirname(abspath(__file__)), ".."))
12+
data_dir = join(medusa_directory,"test","data","")
13+
14+
def create_test_model(model_name="textbook"):
15+
"""Returns an ensemble of models for testing
16+
model_name: str
17+
One of 'ASF356' or 'ASF519'
18+
"""
19+
if model_name == "ASF356":
20+
base_model = cobra.io.load_json_model(join(data_dir, 'ASF356_base_model.json'))
21+
with open(join(data_dir, 'ASF356_base_model_reaction_diffs.json'),'r') as infile:
22+
reaction_diffs = json.load(infile)
23+
24+
elif model_name == "ASF519":
25+
base_model = cobra.io.load_json_model(join(data_dir, 'ASF519_base_model.json'))
26+
with open(join(data_dir, 'ASF519_base_model_reaction_diffs.json'),'r') as infile:
27+
reaction_diffs = json.load(infile)
28+
29+
else:
30+
base_model = cobra.create_test_model(model_name)
31+
32+
33+
34+
test_model = medusa.Ensemble(base_id=base_model.id)
35+
test_model.base_model = base_model
36+
test_model.reaction_diffs = reaction_diffs
37+
return test_model

0 commit comments

Comments
 (0)