cobrak 0.0.1__py3-none-any.whl

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.
cobrak/__init__.py ADDED
@@ -0,0 +1,100 @@
1
+ """
2
+ COBRAk is a general COBRA/COBRA-k suite written as Python package. For more about it, visit its repository:
3
+ https://github.com/klamt-lab/COBRAk
4
+
5
+ The __init__ file of COBRAk initializes the rich text output & tracebacks as well as its logger. Furthermore, graceful
6
+ shutdown of user-induced shutdowns is enabled.
7
+ """
8
+
9
+ import logging
10
+ import signal
11
+ import sys
12
+ from types import FrameType
13
+ from typing import Any, Optional
14
+
15
+ from rich.console import Console
16
+ from rich.logging import RichHandler
17
+ from rich.traceback import install
18
+
19
+
20
+ # PUBLIC FUNCTIONS SECTION #
21
+ def exit_signal_handler(sig: int, frame: FrameType | None) -> None:
22
+ """Handles the exit signal by printing a shutdown message and exiting the program.
23
+
24
+ Args:
25
+ sig (int): The signal number.
26
+ frame (Optional[FrameType]): The current frame.
27
+ """
28
+ print(
29
+ "COBRAk received user signal to terminate (this message may appear multiple times in parallelized contexts). Shutting down..."
30
+ )
31
+ sys.exit(0)
32
+
33
+
34
+ def setup_rich_tracebacks(show_locals: bool) -> None:
35
+ """Sets up rich tracebacks with the given options.
36
+
37
+ Args:
38
+ show_locals (bool): Whether to show local variables in the traceback.
39
+ """
40
+ install(show_locals=show_locals)
41
+
42
+
43
+ def set_logging_level(level: int) -> None:
44
+ """Sets the logging level.
45
+
46
+ E.g. INFO, ERROR, WARNING and CRITICAL from Python's logging module.
47
+
48
+ Args:
49
+ level (int): The logging level.
50
+ """
51
+ logger.setLevel(level)
52
+
53
+
54
+ def set_logging_handler(
55
+ show_path: bool = False,
56
+ show_time: bool = False,
57
+ show_level: bool = True,
58
+ keywords: list[str] = [
59
+ "info",
60
+ "warning",
61
+ "error",
62
+ "critical",
63
+ ],
64
+ **args: Any,
65
+ ) -> RichHandler:
66
+ """
67
+ Sets up the logging handler with the given options.
68
+
69
+ Args:
70
+ show_path (bool, optional): Whether to show the path. Defaults to False.
71
+ show_time (bool, optional): Whether to show the time. Defaults to False.
72
+ show_level (bool, optional): Whether to show the level. Defaults to True.
73
+ keywords (Dict[str, str], optional): The keywords to highlight. Defaults to ["info", "warning", "error", "critical"]
74
+ **args (Any, optional): Additional Rich handler arguments.
75
+ """
76
+ return RichHandler(
77
+ show_path=show_path,
78
+ show_time=show_time,
79
+ show_level=show_level,
80
+ keywords=keywords,
81
+ **args,
82
+ )
83
+
84
+
85
+ # ACTIVE SCRIPT SECTION #
86
+ signal.signal(signal.SIGINT, exit_signal_handler)
87
+ signal.signal(signal.SIGTERM, exit_signal_handler)
88
+
89
+ setup_rich_tracebacks(False)
90
+
91
+ # Create a logger
92
+ logger = logging.getLogger(__name__)
93
+
94
+ # Set the logging level
95
+ set_logging_level(logging.INFO)
96
+
97
+ # Add the handler to the logger
98
+ logger.addHandler(set_logging_handler())
99
+
100
+ console = Console()
cobrak/_efms.py ADDED
@@ -0,0 +1,189 @@
1
+ from copy import deepcopy
2
+ from random import choice
3
+
4
+ from joblib import Parallel, cpu_count, delayed
5
+ from pyomo.environ import (
6
+ Binary,
7
+ ConcreteModel,
8
+ Constraint,
9
+ Objective,
10
+ Reals,
11
+ SolverFactoryClass,
12
+ Var,
13
+ maximize,
14
+ )
15
+
16
+ from .constants import BIG_M, STANDARD_MIN_MDF
17
+ from .dataclasses import Model, Solver
18
+ from .lps import (
19
+ get_lp_from_cobrak_model,
20
+ get_objective,
21
+ )
22
+ from .pyomo_functionality import get_solver
23
+ from .standard_solvers import SCIP
24
+ from .utilities import (
25
+ delete_orphaned_metabolites_and_enzymes,
26
+ delete_unused_reactions_in_variability_dict,
27
+ get_stoichiometrically_coupled_reactions,
28
+ )
29
+
30
+ STANDARD_EFM_OBJ_NAME = "EFM_STANDARD_OBJECTIVE"
31
+
32
+
33
+ def _efm_batch_routine(
34
+ pyomo_model: ConcreteModel,
35
+ pyomo_solver: SolverFactoryClass,
36
+ objective_name: str,
37
+ cobrak_solver: Solver,
38
+ objective_sense: int,
39
+ ) -> None:
40
+ getattr(pyomo_model, objective_name).activate()
41
+
42
+ # OBJ ACTIVATE
43
+ pyomo_solver.solve(
44
+ pyomo_model, tee=True, warmstart=True, **cobrak_solver.solve_extra_options
45
+ )
46
+
47
+ if results.solver.termination_condition == TerminationCondition.maxTimeLimit:
48
+ result = None
49
+ else:
50
+ result = value(getattr(model, target_id))
51
+
52
+ getattr(pyomo_model, objective_name).deactivate()
53
+
54
+
55
+ def calculate_efms(
56
+ cobrak_model: Model,
57
+ with_inhomogenous_constraints: bool = False,
58
+ with_enzyme_constraints: bool = False,
59
+ with_thermodynamic_constraints: bool = False,
60
+ solver: Solver = SCIP,
61
+ variability_dict: dict[str, tuple[float, float]] = {},
62
+ max_num_efms: int = 1_000_000_000,
63
+ min_mdf: float = STANDARD_MIN_MDF,
64
+ min_efm_flux_bound: float = 1e-5,
65
+ ) -> set[tuple[str, ...]]:
66
+ enforced_reacs = [
67
+ reac_id
68
+ for reac_id in variability_dict
69
+ if variability_dict[reac_id][0] > 0.0 and reac_id in cobrak_model.reactions
70
+ ]
71
+ if not with_inhomogenous_constraints:
72
+ for enforced_reac in enforced_reacs:
73
+ cobrak_model.reactions[enforced_reac].min_flux = 0.0
74
+ variability_dict[enforced_reac] = (0.0, variability_dict[enforced_reac][1])
75
+
76
+ reduced_model = deepcopy(cobrak_model)
77
+ if variability_dict != {}:
78
+ reduced_model = delete_unused_reactions_in_variability_dict(
79
+ reduced_model, variability_dict
80
+ )
81
+ reduced_model = delete_orphaned_metabolites_and_enzymes(reduced_model)
82
+
83
+ all_couples = get_stoichiometrically_coupled_reactions(
84
+ reduced_model,
85
+ )
86
+ eligible_couples: list[list[str]] = []
87
+ for couple in all_couples:
88
+ eligible = True
89
+ for enforced_reac in enforced_reacs:
90
+ if enforced_reac in couple:
91
+ eligible = False
92
+ break
93
+ if eligible:
94
+ eligible_couples.append(couple)
95
+
96
+ pyomo_model = get_lp_from_cobrak_model(
97
+ cobrak_model=reduced_model,
98
+ with_enzyme_constraints=with_enzyme_constraints,
99
+ with_thermodynamic_constraints=with_thermodynamic_constraints,
100
+ with_loop_constraints=True,
101
+ min_mdf=min_mdf,
102
+ )
103
+ zc_var_sum = 0.0
104
+ zc_var_ids: list[str] = []
105
+ objective_names: list[str] = []
106
+ for eligible_couple in eligible_couples:
107
+ zc_var_id = "zC_var_" + "_".join(eligible_couple)
108
+ zc_var_ids.append(zc_var_id)
109
+
110
+ setattr(pyomo_model, zc_var_id, Var(within=Binary))
111
+ setattr(
112
+ pyomo_model,
113
+ zc_var_id + "_constraint",
114
+ Constraint(
115
+ rule=getattr(pyomo_model, eligible_couple[0])
116
+ <= BIG_M * getattr(pyomo_model, zc_var_id)
117
+ ),
118
+ )
119
+ setattr(
120
+ pyomo_model,
121
+ zc_var_id + "_constraint_2",
122
+ Constraint(
123
+ rule=getattr(pyomo_model, eligible_couple[0])
124
+ >= min_efm_flux_bound * getattr(pyomo_model, zc_var_id)
125
+ ),
126
+ )
127
+ zc_var_sum += getattr(pyomo_model, zc_var_id)
128
+
129
+ objective_names.append(f"objective_{eligible_couple[0]}")
130
+ setattr(
131
+ pyomo_model,
132
+ objective_names[-1],
133
+ Objective(expr=getattr(pyomo_model, eligible_couple[0]), sense=maximize),
134
+ )
135
+ getattr(pyomo_model, objective_names[-1]).deactivate()
136
+
137
+ objective_names.append(f"objective_{eligible_couple[0]}")
138
+ setattr(
139
+ pyomo_model,
140
+ STANDARD_EFM_OBJ_NAME,
141
+ Objective(expr=0.0, sense=maximize),
142
+ )
143
+ getattr(pyomo_model, STANDARD_EFM_OBJ_NAME).deactivate()
144
+
145
+ setattr(pyomo_model, "zc_var_sum", Var(within=Reals, bounds=(1.0, 10_000.0)))
146
+ setattr(
147
+ pyomo_model,
148
+ "zc_var_sum_constraint_1",
149
+ Constraint(rule=getattr(pyomo_model, "zc_var_sum") == zc_var_sum),
150
+ )
151
+ pyomo_model.obj = get_objective(pyomo_model, "zc_var_sum", -1)
152
+
153
+ integer_cut_i = 0
154
+ pyomo_solver = get_solver(solver.name, solver.solver_options, solver.solver_attrs)
155
+ efms: set[tuple[str, ...]] = set()
156
+ for i in range(max_num_efms):
157
+ opt_targets = [STANDARD_EFM_OBJ_NAME] + [
158
+ choice(objective_names) for _ in range(cpu_count() - 1)
159
+ ]
160
+ min_z_results = Parallel(n_jobs=-1, verbose=10)(
161
+ delayed(_efm_batch_routine)(
162
+ pyomo_model,
163
+ pyomo_solver,
164
+ opt_target,
165
+ +1,
166
+ )
167
+ for opt_target in opt_targets
168
+ )
169
+
170
+ new_efms = min_z_results.difference(efms)
171
+ if len(new_efms) == 0:
172
+ break
173
+ for new_efm in new_efms:
174
+ new_integer_cut = 0.0
175
+ new_integer_cut_sum = 0.0
176
+ for zc_var_id in zc_var_ids:
177
+ if getattr(pyomo_model, zc_var_id).value <= 0.1:
178
+ continue
179
+ new_integer_cut += getattr(pyomo_model, zc_var_id)
180
+ new_integer_cut_sum += 1
181
+ setattr(
182
+ pyomo_model,
183
+ f"integer_cut_{integer_cut_i}",
184
+ Constraint(expr=new_integer_cut <= new_integer_cut_sum - 1),
185
+ )
186
+ integer_cut_i += 1
187
+ efms = efms.union(new_efms)
188
+
189
+ return efms
@@ -0,0 +1,76 @@
1
+ """bigg_parse_metabolites_file.py
2
+
3
+ This module contains a function which transforms a BIGG metabolites .txt list
4
+ into an machine-readable JSON.
5
+ """
6
+
7
+ # IMPORTS SECTION #
8
+ from .io import json_write
9
+
10
+
11
+ # PUBLIC FUNCTIONS SECTION #
12
+ def bigg_parse_metabolites_file(
13
+ bigg_metabolites_txt_path: str,
14
+ bigg_metabolites_json_path: str,
15
+ ) -> None:
16
+ """Parses a BIGG metabolites text file and returns a dictionary for this file.
17
+
18
+ As of Sep 14 2024, a BIGG metabolites list of all BIGG-included metabolites
19
+ is retrievable under http://bigg.ucsd.edu/data_access
20
+
21
+ Arguments
22
+ ----------
23
+ * bigg_metabolites_file_path: str ~ The file path to the BIGG metabolites file.
24
+ The usual file name (which has to be included too in this argument) is
25
+ bigg_models_metabolites.txt
26
+ * output_folder: str ~ The folder in which the JSON including the parsed BIGG
27
+ metabolites file data is stored with the name 'bigg_id_name_mapping.json'
28
+
29
+ Output
30
+ ----------
31
+ * A JSON file with the name 'bigg_id_name_mapping.json' in the given output folder,
32
+ with the following structure:
33
+ <pre>
34
+ {
35
+ "$BIGG_ID": "$CHEMICAL_OR_USUAL_NAME",
36
+ (...),
37
+ "$BIGG_ID": "$BIGG_ID",
38
+ (...),
39
+ }
40
+ </pre>
41
+ The BIGG ID <-> BIGG ID mapping is done for models which already use the BIGG IDs.
42
+ """
43
+ # Open the BIGG metabolites file as string list, and remove all newlines
44
+ with open(bigg_metabolites_txt_path, encoding="utf-8") as f:
45
+ lines = f.readlines()
46
+ lines = [x.replace("\n", "") for x in lines if len(x) > 0]
47
+
48
+ # Mapping variable which will store the BIGG ID<->
49
+ bigg_id_name_mapping = {}
50
+ # Go through each BIGG metabolites file line (which is a tab-separated file)
51
+ # and retrieve the BIGG ID and the name (if there is a name for the given BIGG
52
+ # ID)
53
+ for line in lines:
54
+ bigg_id = line.split("\t")[1]
55
+ bigg_id_name_mapping[bigg_id] = bigg_id
56
+
57
+ # Exception to check if there is no name :O
58
+ try:
59
+ name = line.split("\t")[2].lower()
60
+ except Exception:
61
+ continue
62
+ bigg_id_name_mapping[name] = bigg_id
63
+
64
+ try:
65
+ database_links = line.split("\t")[4]
66
+ except Exception:
67
+ continue
68
+ for database_link_part in database_links.split(": "):
69
+ if "CHEBI:" not in database_link_part:
70
+ continue
71
+ subpart = database_link_part.split("CHEBI:")[1].strip()
72
+ chebi_id = subpart.split("; ")[0] if "; " in subpart else subpart
73
+ bigg_id_name_mapping[chebi_id] = bigg_id
74
+
75
+ # Write the JSON in the given folder :D
76
+ json_write(bigg_metabolites_json_path, bigg_id_name_mapping)