pycomo 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycomo-0.1.0/LICENSE +21 -0
- pycomo-0.1.0/PKG-INFO +36 -0
- pycomo-0.1.0/README.md +22 -0
- pycomo-0.1.0/setup.cfg +4 -0
- pycomo-0.1.0/setup.py +32 -0
- pycomo-0.1.0/src/pycomo/__init__.py +16 -0
- pycomo-0.1.0/src/pycomo/helper/__init__.py +0 -0
- pycomo-0.1.0/src/pycomo/helper/cli.py +127 -0
- pycomo-0.1.0/src/pycomo/helper/utils.py +368 -0
- pycomo-0.1.0/src/pycomo/pycomo_models.py +1598 -0
- pycomo-0.1.0/src/pycomo.egg-info/PKG-INFO +36 -0
- pycomo-0.1.0/src/pycomo.egg-info/SOURCES.txt +17 -0
- pycomo-0.1.0/src/pycomo.egg-info/dependency_links.txt +1 -0
- pycomo-0.1.0/src/pycomo.egg-info/entry_points.txt +2 -0
- pycomo-0.1.0/src/pycomo.egg-info/requires.txt +4 -0
- pycomo-0.1.0/src/pycomo.egg-info/top_level.txt +1 -0
- pycomo-0.1.0/test/test_pycomo_doall.py +15 -0
- pycomo-0.1.0/test/test_pycomo_singleorganismmodel.py +7 -0
- pycomo-0.1.0/test/test_pycomo_utils.py +58 -0
pycomo-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Michael Predl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pycomo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pycomo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: PyCoMo is a software package for generating and analysing compartmentalized community metabolic models
|
|
5
|
+
Home-page: https://github.com/univieCUBE/PyCoMo
|
|
6
|
+
Author: Michael Predl
|
|
7
|
+
Author-email: michael.predl@univie.ac.at
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# PyCoMo
|
|
16
|
+
## What is PyCoMo?
|
|
17
|
+
PyCoMo is a python 3 package for the creation and analysis of community metabolic models. More specifically, PyCoMo generates compartmentalized community metabolic models with a structure allowing simulations under fixed growth rate, but variable abundance profile, or fixed abundance profile and variable community growth rate. The community metabolic models generated by PyCoMo can switch between these two structures and retain the original reaction bounds of the input member models. As also all metabolites, reactions, genes and compartments are directly attributable to their member of origin, PyCoMo community metabolic models are fully reusable.
|
|
18
|
+
|
|
19
|
+
The community models can be analysed with PyCoMo to predict all feasible exchange metabolites and cross-feeding interactions, for the whole space of growth rate and abundance profiles. The community models are COBRApy models and can therefor be directly used by other COBRA methods. It is also possible to save and load the community models in SBML format, allowing to share and reuse the models built with PyCoMo.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
For installing PyCoMo download or clone the repository to your machine.
|
|
23
|
+
```
|
|
24
|
+
git clone https://github.com/univieCUBE/PyCoMo
|
|
25
|
+
```
|
|
26
|
+
Run pip install on the folder containing the PyCoMo repository.
|
|
27
|
+
```
|
|
28
|
+
pip install path/to/PyCoMo
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage guide
|
|
32
|
+
PyCoMo can be imported in Python as any other package. Please look through the tutorial for a walkthrough of all the options generating and analysing community metabolic models (available as ipython notebook, python file and pdf).
|
|
33
|
+
|
|
34
|
+
PyCoMo can also be used via its command line interface. After installation, run ```pycomo -h``` or ```pycomo --help``` to see all options.
|
|
35
|
+
## Citing PyCoMo
|
|
36
|
+
At the present moment we are still working on the final stages of the manuscript. Once it is made public, a citation note will be included at this place.
|
pycomo-0.1.0/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# PyCoMo
|
|
2
|
+
## What is PyCoMo?
|
|
3
|
+
PyCoMo is a python 3 package for the creation and analysis of community metabolic models. More specifically, PyCoMo generates compartmentalized community metabolic models with a structure allowing simulations under fixed growth rate, but variable abundance profile, or fixed abundance profile and variable community growth rate. The community metabolic models generated by PyCoMo can switch between these two structures and retain the original reaction bounds of the input member models. As also all metabolites, reactions, genes and compartments are directly attributable to their member of origin, PyCoMo community metabolic models are fully reusable.
|
|
4
|
+
|
|
5
|
+
The community models can be analysed with PyCoMo to predict all feasible exchange metabolites and cross-feeding interactions, for the whole space of growth rate and abundance profiles. The community models are COBRApy models and can therefor be directly used by other COBRA methods. It is also possible to save and load the community models in SBML format, allowing to share and reuse the models built with PyCoMo.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
For installing PyCoMo download or clone the repository to your machine.
|
|
9
|
+
```
|
|
10
|
+
git clone https://github.com/univieCUBE/PyCoMo
|
|
11
|
+
```
|
|
12
|
+
Run pip install on the folder containing the PyCoMo repository.
|
|
13
|
+
```
|
|
14
|
+
pip install path/to/PyCoMo
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage guide
|
|
18
|
+
PyCoMo can be imported in Python as any other package. Please look through the tutorial for a walkthrough of all the options generating and analysing community metabolic models (available as ipython notebook, python file and pdf).
|
|
19
|
+
|
|
20
|
+
PyCoMo can also be used via its command line interface. After installation, run ```pycomo -h``` or ```pycomo --help``` to see all options.
|
|
21
|
+
## Citing PyCoMo
|
|
22
|
+
At the present moment we are still working on the final stages of the manuscript. Once it is made public, a citation note will be included at this place.
|
pycomo-0.1.0/setup.cfg
ADDED
pycomo-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r") as f:
|
|
4
|
+
long_description = f.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="pycomo",
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
description="PyCoMo is a software package for generating and analysing compartmentalized community metabolic models",
|
|
10
|
+
package_dir={"": "src"},
|
|
11
|
+
packages=find_packages(where="src"),
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/univieCUBE/PyCoMo",
|
|
15
|
+
author="Michael Predl",
|
|
16
|
+
author_email="michael.predl@univie.ac.at",
|
|
17
|
+
license="MIT",
|
|
18
|
+
classifiers=[
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3"
|
|
21
|
+
],
|
|
22
|
+
entry_points={
|
|
23
|
+
'console_scripts': [
|
|
24
|
+
'pycomo = pycomo.pycomo_models:main'
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
install_requires=["cobra >= 0.23.0", "pandas >= 1.5.3", "python-libsbml >= 5.20.1", "numpy >= 1.22.4"],
|
|
28
|
+
extra_require={
|
|
29
|
+
"dev": ["pytest >= 7.0", "twine >= 4.0.2"],
|
|
30
|
+
},
|
|
31
|
+
python_requires=">=3.9",
|
|
32
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__author__ = "Michael Predl"
|
|
2
|
+
__version__ = "0.1.0"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from pycomo.pycomo_models import (
|
|
6
|
+
SingleOrganismModel,
|
|
7
|
+
CommunityModel,
|
|
8
|
+
doall
|
|
9
|
+
)
|
|
10
|
+
from pycomo.helper.utils import (
|
|
11
|
+
load_named_model,
|
|
12
|
+
load_named_models_from_dir,
|
|
13
|
+
read_medium_from_file,
|
|
14
|
+
read_abundance_from_file,
|
|
15
|
+
make_string_sbml_id_compatible,
|
|
16
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
__description__ = ('A package for generating community metabolic models from single species/strain models.')
|
|
6
|
+
__author__ = 'Michael Predl'
|
|
7
|
+
__license__ = "MIT"
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_arg_parser():
|
|
12
|
+
parser = argparse.ArgumentParser(prog="PyCoMo")
|
|
13
|
+
|
|
14
|
+
parser.add_argument('-v', '--version', action='version', version=f'%(prog)s {__version__}',
|
|
15
|
+
help="display PyCoMo version")
|
|
16
|
+
|
|
17
|
+
parser.add_argument('-i', '--input', nargs='+', type=str, required=True,
|
|
18
|
+
help="single species/strain models to combine, either as a directory or separate files")
|
|
19
|
+
|
|
20
|
+
parser.add_argument('-c', '--is-community', action='store_true',
|
|
21
|
+
help="Set this flag if the input model is already a community model.")
|
|
22
|
+
|
|
23
|
+
# All parameters regarding the generation and contextualisation of the community model
|
|
24
|
+
pg_com_model = parser.add_argument_group('Community model parameters')
|
|
25
|
+
|
|
26
|
+
pg_com_model.add_argument('-n', '--name', type=str, default='community_model',
|
|
27
|
+
help="the name for the new community model")
|
|
28
|
+
|
|
29
|
+
pg_com_model.add_argument('-m', '--match-via-annotation', type=str,
|
|
30
|
+
help="the metabolite annotation type to use for matching exchange metabolites of "
|
|
31
|
+
"different community members (e.g. metanetx.chemical)")
|
|
32
|
+
|
|
33
|
+
pg_linearisation = pg_com_model.add_mutually_exclusive_group()
|
|
34
|
+
|
|
35
|
+
pg_linearisation.add_argument('--growth-rate', type=float,
|
|
36
|
+
help="set abundances to be equal for all community members")
|
|
37
|
+
|
|
38
|
+
pg_linearisation.add_argument('--equal-abd', action='store_true',
|
|
39
|
+
help="set abundances to be equal for all community members")
|
|
40
|
+
|
|
41
|
+
pg_linearisation.add_argument('--abd-file', type=str,
|
|
42
|
+
help="a comma separated file containing the input model file names and their "
|
|
43
|
+
"abundance. No header should be used in the file.")
|
|
44
|
+
|
|
45
|
+
pg_com_model.add_argument('--medium', type=str,
|
|
46
|
+
help="the medium to be used in the community model, as a comma separated file "
|
|
47
|
+
"containing a column 'compounds' and a column 'maxFlux'.")
|
|
48
|
+
|
|
49
|
+
# All parameters regarding outputs to be produced
|
|
50
|
+
pg_output = parser.add_argument_group('Output parameters')
|
|
51
|
+
|
|
52
|
+
pg_output.add_argument('-o', '--output-dir', default=os.getcwd(), type=str,
|
|
53
|
+
help="the output directory to store results (default is the current working directory)")
|
|
54
|
+
|
|
55
|
+
pg_output.add_argument('--fba-flux', action='store_true',
|
|
56
|
+
help="run FBA on the community model and store the flux vector in a file")
|
|
57
|
+
|
|
58
|
+
pg_output.add_argument('--fva-flux', type=float,
|
|
59
|
+
help="run FVA on the exchange metabolites of the community model and store the flux vector "
|
|
60
|
+
"in a file. Set the threshold of the objective that needs to be achieved.")
|
|
61
|
+
|
|
62
|
+
pg_output.add_argument('--fba-interaction', action='store_true',
|
|
63
|
+
help="run FBA on the community model and store the flux of exchange metabolites and "
|
|
64
|
+
"whether they are cross-fed in a file")
|
|
65
|
+
|
|
66
|
+
pg_output.add_argument('--fva-interaction', action='store_true',
|
|
67
|
+
help="run FVA on the community model and store the flux of exchange metabolites and "
|
|
68
|
+
"whether they are cross-fed in a file. Set the threshold of the objective that needs "
|
|
69
|
+
"to be achieved.")
|
|
70
|
+
|
|
71
|
+
if len(sys.argv) == 1:
|
|
72
|
+
parser.print_help(sys.stderr)
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
return parser
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_args(args):
|
|
79
|
+
if not os.path.isdir(args.output_dir):
|
|
80
|
+
raise ValueError("The output-dir does not exist or is not a directory.")
|
|
81
|
+
if args.input is None:
|
|
82
|
+
raise ValueError("Please provide input to run PyCoMo (-i / --input)")
|
|
83
|
+
elif not all([os.path.exists(arg_path) for arg_path in args.input]):
|
|
84
|
+
raise ValueError("Not all input files / directories exist.")
|
|
85
|
+
|
|
86
|
+
if args.fva_flux is not None:
|
|
87
|
+
if not 0. <= args.fva_flux <= 1.:
|
|
88
|
+
raise ValueError("The fva-flux argument needs to be between 0. and 1. (inclusive).")
|
|
89
|
+
|
|
90
|
+
if args.fva_flux is not None:
|
|
91
|
+
if not 0. <= args.fva_flux <= 1.:
|
|
92
|
+
raise ValueError("The fva-flux argument needs to be between 0. and 1. (inclusive).")
|
|
93
|
+
|
|
94
|
+
if args.abd_file is not None and not os.path.isfile(args.abd_file):
|
|
95
|
+
raise ValueError("The abundance file is not a file or does not exist")
|
|
96
|
+
|
|
97
|
+
if args.growth_rate is not None and args.growth_rate < 0.:
|
|
98
|
+
raise ValueError(f"The specified growth rate ({args.growth_rate}) is negative.")
|
|
99
|
+
|
|
100
|
+
if args.medium is not None and not os.path.isfile(args.medium):
|
|
101
|
+
raise ValueError("The medium file is not a file or does not exist")
|
|
102
|
+
|
|
103
|
+
args.abundance = None
|
|
104
|
+
if args.equal_abd:
|
|
105
|
+
args.abundance = "equal"
|
|
106
|
+
elif args.abd_file is not None:
|
|
107
|
+
args.abundance = args.abd_file
|
|
108
|
+
|
|
109
|
+
args.fba_solution_path = None
|
|
110
|
+
if args.fba_flux:
|
|
111
|
+
args.fba_solution_path = os.path.join(args.output_dir, f"{args.name}_fba_flux.csv")
|
|
112
|
+
|
|
113
|
+
args.fva_solution_path = None
|
|
114
|
+
if args.fva_flux is not None:
|
|
115
|
+
args.fva_solution_path = os.path.join(args.output_dir, f"{args.name}_fva_{args.fva_flux}_flux.csv")
|
|
116
|
+
|
|
117
|
+
args.fba_interaction_path = None
|
|
118
|
+
if args.fba_interaction:
|
|
119
|
+
args.fba_interaction_path = os.path.join(args.output_dir, f"{args.name}_fba_flux.csv")
|
|
120
|
+
|
|
121
|
+
args.fva_interaction_path = None
|
|
122
|
+
if args.fva_interaction:
|
|
123
|
+
args.fva_interaction_path = os.path.join(args.output_dir, f"{args.name}_fva_{args.fva_interaction}_flux.csv")
|
|
124
|
+
|
|
125
|
+
args.sbml_output_path = os.path.join(args.output_dir, f"{args.name}.xml")
|
|
126
|
+
|
|
127
|
+
return args
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains some utility function related to cobrapy community models.
|
|
3
|
+
"""
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import cobra
|
|
6
|
+
import libsbml
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_string_sbml_id_compatible(string, remove_ascii_escapes=False, remove_trailing_underscore=False):
|
|
12
|
+
"""
|
|
13
|
+
This function
|
|
14
|
+
:param string:
|
|
15
|
+
:return:
|
|
16
|
+
"""
|
|
17
|
+
alphanum_pattern = re.compile(r'\w')
|
|
18
|
+
|
|
19
|
+
for idx, character in enumerate(string):
|
|
20
|
+
if not alphanum_pattern.match(character):
|
|
21
|
+
string = string[:idx] + "_" + string[idx+1:]
|
|
22
|
+
|
|
23
|
+
if remove_ascii_escapes:
|
|
24
|
+
string = remove_ascii_escape_from_string(string)
|
|
25
|
+
|
|
26
|
+
if remove_trailing_underscore:
|
|
27
|
+
while string and string[-1] == "_":
|
|
28
|
+
string = string[:-1]
|
|
29
|
+
|
|
30
|
+
if string[0].isdigit():
|
|
31
|
+
string = "_" + string
|
|
32
|
+
|
|
33
|
+
return string
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def remove_ascii_escape_from_string(text):
|
|
37
|
+
ascii_pattern = re.compile("__\d+__")
|
|
38
|
+
while re.search(ascii_pattern, text):
|
|
39
|
+
text = re.sub(ascii_pattern, remove_dunder_from_ascii_escape, text)
|
|
40
|
+
return text
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def remove_dunder_from_ascii_escape(match_obj):
|
|
44
|
+
if match_obj.group() is not None:
|
|
45
|
+
print(match_obj.group())
|
|
46
|
+
return match_obj.group()[1:-1]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def read_medium_from_file(file, comp):
|
|
50
|
+
medium_df = pd.read_csv(file, sep=",")
|
|
51
|
+
medium_dict = {}
|
|
52
|
+
for idx, row in medium_df.iterrows():
|
|
53
|
+
met = row["compounds"]
|
|
54
|
+
flux = float(row["maxFlux"])
|
|
55
|
+
rxn = "EX_" + met + comp
|
|
56
|
+
medium_dict[rxn] = flux
|
|
57
|
+
return medium_dict
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def read_abundance_from_file(file):
|
|
61
|
+
endings = {"sbml", "json", "mat", "yaml", "yml"}
|
|
62
|
+
abd_df = pd.read_csv(file, sep=",")
|
|
63
|
+
abd_dict = {}
|
|
64
|
+
assert len(abd_df.columns) == 2
|
|
65
|
+
abd_df.columns = ["model", "fraction"]
|
|
66
|
+
for idx, row in abd_df.iterrows():
|
|
67
|
+
model = row["model"]
|
|
68
|
+
if str(os.path.splitext(model)[1]) in endings:
|
|
69
|
+
model = model.replace(str(os.path.splitext(file)[1]), "")
|
|
70
|
+
fraction = float(row["fraction"])
|
|
71
|
+
abd_dict[model] = fraction
|
|
72
|
+
return abd_dict
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_named_model(file, format="sbml"):
|
|
76
|
+
name = os.path.split(file)[1].replace(str(os.path.splitext(file)[1]), "")
|
|
77
|
+
if format == "sbml":
|
|
78
|
+
model = cobra.io.read_sbml_model(file)
|
|
79
|
+
elif format == "json":
|
|
80
|
+
model = cobra.io.load_json_model(file)
|
|
81
|
+
elif format == "mat":
|
|
82
|
+
model = cobra.io.load_matlab_model(file)
|
|
83
|
+
elif format in ["yaml", "yml"]:
|
|
84
|
+
model = cobra.io.load_yaml_model(file)
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError(f"Incorrect format for model. Please use either sbml or json.")
|
|
87
|
+
return model, name
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_named_models_from_dir(path, format="sbml"):
|
|
91
|
+
endings = {"sbml": [".xml"], "json": [".json"], "mat": [".mat"], "yaml": [".yaml", ".yml"]}
|
|
92
|
+
named_models = {}
|
|
93
|
+
files = os.listdir(path)
|
|
94
|
+
expected_ending = endings[format]
|
|
95
|
+
for file in files:
|
|
96
|
+
if not os.path.isfile(os.path.join(path, file)) or str(os.path.splitext(file)[1]) not in expected_ending:
|
|
97
|
+
continue
|
|
98
|
+
else:
|
|
99
|
+
model, name = load_named_model(os.path.join(path, file), format=format)
|
|
100
|
+
named_models[name] = model
|
|
101
|
+
return named_models
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def close_to_zero(num, t=10**-10):
|
|
105
|
+
return -t < num < t
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_model_biomass_compound(model, shared_compartment_name, expected_biomass_id="", generate_if_none=False):
|
|
109
|
+
"""This will produce a biomass metabolite with a unique production reaction"""
|
|
110
|
+
objective = str(model.objective.expression).split("*")[1].split(' ')[0]
|
|
111
|
+
biomass_rxn = model.reactions.get_by_id(objective)
|
|
112
|
+
biomass_products = model.reactions.get_by_id(objective).products
|
|
113
|
+
biomass_met = None
|
|
114
|
+
if len(expected_biomass_id) > 0:
|
|
115
|
+
if expected_biomass_id in [met.id for met in biomass_products]:
|
|
116
|
+
biomass_met = model.metabolites.get_by_id(expected_biomass_id)
|
|
117
|
+
elif expected_biomass_id in [met.id for met in model.metabolites]:
|
|
118
|
+
print(f"WARNING: expected biomass id {expected_biomass_id} is not a product of the objective function.")
|
|
119
|
+
biomass_met = model.metabolites.get_by_id(expected_biomass_id)
|
|
120
|
+
else:
|
|
121
|
+
raise AssertionError(f"Expected biomass metabolite {expected_biomass_id} is not found in the model.")
|
|
122
|
+
elif len(biomass_products) == 0:
|
|
123
|
+
# No metabolites produced
|
|
124
|
+
if generate_if_none:
|
|
125
|
+
print(f"Note: no products in the objective function, adding biomass to it.")
|
|
126
|
+
biomass_met = cobra.Metabolite(f"cpd11416_{shared_compartment_name}", name='Biomass',
|
|
127
|
+
compartment=shared_compartment_name)
|
|
128
|
+
model.add_metabolites([biomass_met])
|
|
129
|
+
biomass_rxn.add_metabolites({biomass_met: 1.})
|
|
130
|
+
else:
|
|
131
|
+
raise AssertionError(f"No biomass compound could be found in objective\nObjective id: {objective}")
|
|
132
|
+
elif len(biomass_products) == 1:
|
|
133
|
+
biomass_met = biomass_products[0]
|
|
134
|
+
else:
|
|
135
|
+
# Multiple products in the objective, making biomass metabolites ambiguous
|
|
136
|
+
if generate_if_none:
|
|
137
|
+
print(f"Note: no products in the objective function, adding biomass to it.")
|
|
138
|
+
biomass_met = cobra.Metabolite(f"cpd11416_{shared_compartment_name}", name='Biomass',
|
|
139
|
+
compartment=shared_compartment_name)
|
|
140
|
+
model.add_metabolites([biomass_met])
|
|
141
|
+
biomass_rxn.add_metabolites({biomass_met: 1.})
|
|
142
|
+
else:
|
|
143
|
+
raise AssertionError(f"Multiple products in objective, biomass metabolite is ambiguous. Please set it "
|
|
144
|
+
f"manually.\nObjective id: {objective}")
|
|
145
|
+
return biomass_met
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def make_model_ids_sbml_conform(model):
|
|
149
|
+
for met in model.metabolites:
|
|
150
|
+
if not met.name:
|
|
151
|
+
met.name = met.id
|
|
152
|
+
met.id = make_string_sbml_id_compatible(met.id, remove_ascii_escapes=True, remove_trailing_underscore=True)
|
|
153
|
+
met.compartment = make_string_sbml_id_compatible(met.compartment, remove_ascii_escapes=True, remove_trailing_underscore=True)
|
|
154
|
+
for rxn in model.reactions:
|
|
155
|
+
if not rxn.name:
|
|
156
|
+
rxn.name = rxn.id
|
|
157
|
+
rxn.id = make_string_sbml_id_compatible(rxn.id, remove_ascii_escapes=True, remove_trailing_underscore=True)
|
|
158
|
+
for group in model.groups:
|
|
159
|
+
if not group.name:
|
|
160
|
+
group.name = group.id
|
|
161
|
+
group.id = make_string_sbml_id_compatible(group.id, remove_ascii_escapes=True, remove_trailing_underscore=True)
|
|
162
|
+
|
|
163
|
+
rename_dict = {}
|
|
164
|
+
for gene in model.genes:
|
|
165
|
+
if not gene.name:
|
|
166
|
+
gene.name = gene.id
|
|
167
|
+
rename_dict[gene.id] = make_string_sbml_id_compatible(gene.id, remove_ascii_escapes=True, remove_trailing_underscore=True)
|
|
168
|
+
|
|
169
|
+
if rename_dict:
|
|
170
|
+
cobra.manipulation.modify.rename_genes(model, rename_dict)
|
|
171
|
+
|
|
172
|
+
model.repair()
|
|
173
|
+
|
|
174
|
+
return model
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_metabolite_id_without_compartment(metabolite):
|
|
178
|
+
compartment_string = "_" + metabolite.compartment
|
|
179
|
+
if metabolite.id[-len(compartment_string):] == compartment_string:
|
|
180
|
+
return metabolite.id[:-len(compartment_string)]
|
|
181
|
+
else:
|
|
182
|
+
return metabolite.id
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def list_contains_unique_strings(str_list):
|
|
186
|
+
return len(str_list) == len(list(set(str_list)))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def list_of_strings_is_self_contained(str_list):
|
|
190
|
+
self_contained = False
|
|
191
|
+
for idx, string in enumerate(str_list):
|
|
192
|
+
for other_idx in range(len(str_list)):
|
|
193
|
+
if idx == other_idx: continue
|
|
194
|
+
elif string in str_list[other_idx]:
|
|
195
|
+
self_contained = True
|
|
196
|
+
return self_contained
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def list_without_element(list_var, element):
|
|
200
|
+
list_var = list_var.copy()
|
|
201
|
+
list_var.remove(element)
|
|
202
|
+
return list_var
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def check_metabolite_equal_mass(met1, met2):
|
|
206
|
+
"""
|
|
207
|
+
This function compares mass and charge of two metabolites. It returns True if the metabolites have equal mass and
|
|
208
|
+
charge and False if they do not.
|
|
209
|
+
"""
|
|
210
|
+
test_reaction = cobra.Reaction()
|
|
211
|
+
test_reaction.add_metabolites({met1: -1., met2: 1.})
|
|
212
|
+
return not bool(test_reaction.check_mass_balance())
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_exchange_metabolites(model):
|
|
216
|
+
exchange_metabolites = {}
|
|
217
|
+
for reaction in model.exchanges:
|
|
218
|
+
if len(reaction.metabolites) != 1:
|
|
219
|
+
print(f"Error: exchange reaction {reaction.id} has more than 1 metabolite")
|
|
220
|
+
exchange_met = list(reaction.metabolites.keys())[0]
|
|
221
|
+
exchange_metabolites[exchange_met.id] = exchange_met
|
|
222
|
+
return exchange_metabolites
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def check_mass_balance_of_metabolites_with_identical_id(model_1, model_2):
|
|
226
|
+
exchg_mets_1 = get_exchange_metabolites(model_1)
|
|
227
|
+
exchg_mets_2 = get_exchange_metabolites(model_2)
|
|
228
|
+
|
|
229
|
+
unbalanced_metabolites = []
|
|
230
|
+
|
|
231
|
+
for met_id in set(exchg_mets_1) & set(exchg_mets_2):
|
|
232
|
+
equal_mass = check_metabolite_equal_mass(exchg_mets_1[met_id], exchg_mets_2[met_id])
|
|
233
|
+
if not equal_mass:
|
|
234
|
+
unbalanced_metabolites.append(met_id)
|
|
235
|
+
|
|
236
|
+
return unbalanced_metabolites
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def create_parameter_in_sbml_model(sbml_model, pid, is_constant, value=None, as_name=False):
|
|
240
|
+
"""
|
|
241
|
+
Helper function to set a parameter with ID and value to a SBML model.
|
|
242
|
+
"""
|
|
243
|
+
parameter = sbml_model.createParameter()
|
|
244
|
+
parameter.setId(pid)
|
|
245
|
+
if value is not None:
|
|
246
|
+
if as_name:
|
|
247
|
+
parameter.setName(value)
|
|
248
|
+
else:
|
|
249
|
+
parameter.setValue(value)
|
|
250
|
+
parameter.setConstant(is_constant)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def create_abundance_parameter(sbml_model, member_id, abundance=None):
|
|
254
|
+
parameter_prefix = "Abundance_"
|
|
255
|
+
create_parameter_in_sbml_model(sbml_model=sbml_model, pid=parameter_prefix+member_id, is_constant=False, value=abundance)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def read_sbml_model_from_file(file_path):
|
|
259
|
+
sbml_doc = cobra.io.sbml._get_doc_from_filename(file_path)
|
|
260
|
+
return sbml_doc.getModel()
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_abundance_parameters_from_sbml_doc(sbml_model):
|
|
264
|
+
parameter_prefix = "Abundance_"
|
|
265
|
+
abundance_dict = {}
|
|
266
|
+
|
|
267
|
+
for parameter in sbml_model.getListOfParameters():
|
|
268
|
+
parameter_id = parameter.getId()
|
|
269
|
+
if parameter_prefix in parameter_id[:len(parameter_prefix)]:
|
|
270
|
+
fraction = None
|
|
271
|
+
if parameter.isSetValue():
|
|
272
|
+
fraction = parameter.getValue()
|
|
273
|
+
abundance_dict[parameter_id[len(parameter_prefix):]] = fraction
|
|
274
|
+
|
|
275
|
+
return abundance_dict
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_flags_and_muc_from_sbml_file(sbml_file):
|
|
279
|
+
sbml_model = read_sbml_model_from_file(sbml_file)
|
|
280
|
+
|
|
281
|
+
parameter_dict = {}
|
|
282
|
+
|
|
283
|
+
for parameter in sbml_model.getListOfParameters():
|
|
284
|
+
parameter_id = parameter.getId()
|
|
285
|
+
if parameter_id == "mu_c":
|
|
286
|
+
value = 1.
|
|
287
|
+
if parameter.isSetValue():
|
|
288
|
+
value = parameter.getValue()
|
|
289
|
+
parameter_dict["mu_c"] = value
|
|
290
|
+
if parameter_id == "fixed_abundance_flag":
|
|
291
|
+
value = 0
|
|
292
|
+
if parameter.isSetValue():
|
|
293
|
+
value = parameter.getValue()
|
|
294
|
+
value = value == 1 # Flags are stored as 1 and 0 for True and False. None is converted to False
|
|
295
|
+
parameter_dict["fixed_abundance_flag"] = value
|
|
296
|
+
if parameter_id == "shared_compartment_id":
|
|
297
|
+
if not parameter.isSetName():
|
|
298
|
+
raise ValueError("Error: Missing parameter shared_compartment_id (parameter name should contain ID of "
|
|
299
|
+
"the shared compartment)")
|
|
300
|
+
name = parameter.getName()
|
|
301
|
+
parameter_dict["shared_compartment_name"] = name
|
|
302
|
+
if parameter_id == "fixed_growth_rate_flag":
|
|
303
|
+
value = 0
|
|
304
|
+
if parameter.isSetValue():
|
|
305
|
+
value = parameter.getValue()
|
|
306
|
+
value = value == 1 # Flags are stored as 1 and 0 for True and False. None is converted to False
|
|
307
|
+
parameter_dict["fixed_growth_rate_flag"] = value
|
|
308
|
+
|
|
309
|
+
return parameter_dict
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_abundance_parameters_from_sbml_file(sbml_file):
|
|
313
|
+
sbml_model = read_sbml_model_from_file(sbml_file)
|
|
314
|
+
return get_abundance_parameters_from_sbml_doc(sbml_model)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def find_matching_annotations(met1, met2):
|
|
318
|
+
shared_annotation_keys = set(met1.annotation) & set(met2.annotation)
|
|
319
|
+
matching_annotations = {}
|
|
320
|
+
for key in shared_annotation_keys:
|
|
321
|
+
if met1.annotation[key] == met2.annotation[key]:
|
|
322
|
+
matching_annotations[key] = met1.annotation[key]
|
|
323
|
+
|
|
324
|
+
return matching_annotations
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def check_annotation_overlap_of_metabolites_with_identical_id(model_1, model_2):
|
|
328
|
+
exchg_mets_1 = get_exchange_metabolites(model_1)
|
|
329
|
+
exchg_mets_2 = get_exchange_metabolites(model_2)
|
|
330
|
+
|
|
331
|
+
metabolites_without_overlap = []
|
|
332
|
+
|
|
333
|
+
for met_id in set(exchg_mets_1) & set(exchg_mets_2):
|
|
334
|
+
matching_annotations = find_matching_annotations(exchg_mets_1[met_id], exchg_mets_2[met_id])
|
|
335
|
+
if not matching_annotations:
|
|
336
|
+
metabolites_without_overlap.append(met_id)
|
|
337
|
+
|
|
338
|
+
return metabolites_without_overlap
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def relax_reaction_constraints_for_zero_flux(model):
|
|
342
|
+
"""This function relaxes all constraints of a model to allow a flux of 0 in all reactions."""
|
|
343
|
+
for reaction in model.reactions:
|
|
344
|
+
if reaction.lower_bound > 0.:
|
|
345
|
+
reaction.lower_bound = 0.
|
|
346
|
+
if reaction.upper_bound < 0.:
|
|
347
|
+
reaction.upper_bound = 0.
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def find_loops_in_model(model):
|
|
351
|
+
"""This function finds loops in models. This is accomplished by setting the medium to contain nothing and relax
|
|
352
|
+
all constraints to allow a flux of 0. Then, FVA is run on all reactions"""
|
|
353
|
+
loop_model = model.copy()
|
|
354
|
+
loop_model.medium = {}
|
|
355
|
+
relax_reaction_constraints_for_zero_flux(loop_model)
|
|
356
|
+
no_medium = {}
|
|
357
|
+
max_flux_value = 1000.0
|
|
358
|
+
loops = []
|
|
359
|
+
for rxn in loop_model.reactions:
|
|
360
|
+
loop_model.objective = rxn.id
|
|
361
|
+
solution = loop_model.optimize("minimize")
|
|
362
|
+
min_flux = solution.objective_value if not solution.status == "infeasible" else 0.
|
|
363
|
+
solution = loop_model.optimize("maximize")
|
|
364
|
+
max_flux = solution.objective_value if not solution.status == "infeasible" else 0.
|
|
365
|
+
if min_flux != 0. or max_flux != 0.:
|
|
366
|
+
loops.append({"reaction": rxn.id, "min_flux": min_flux, "max_flux": max_flux})
|
|
367
|
+
loops_df = pd.DataFrame(loops)
|
|
368
|
+
return loops_df
|