mptool 1.0.0__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.
- mptool/__init__.py +107 -0
- mptool/__main__.py +430 -0
- mptool/consistency.py +365 -0
- mptool/fva.py +1207 -0
- mptool/io.py +289 -0
- mptool/mapping.py +237 -0
- mptool/model.py +636 -0
- mptool/mps.py +2793 -0
- mptool/preprocess.py +389 -0
- mptool/reproduce.py +361 -0
- mptool/validate.py +284 -0
- mptool-1.0.0.dist-info/METADATA +399 -0
- mptool-1.0.0.dist-info/RECORD +17 -0
- mptool-1.0.0.dist-info/WHEEL +5 -0
- mptool-1.0.0.dist-info/licenses/LICENSE +21 -0
- mptool-1.0.0.dist-info/top_level.txt +1 -0
- mptool-1.0.0.dist-info/zip-safe +1 -0
mptool/__init__.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# (c) 2019-2026 ETH Zurich, Ove Øyås
|
|
4
|
+
# Licensed under the MIT License. See LICENSE file for details.
|
|
5
|
+
"""
|
|
6
|
+
mptool — enumeration and sampling of minimal pathways in metabolic (sub)networks.
|
|
7
|
+
|
|
8
|
+
Public functions::
|
|
9
|
+
|
|
10
|
+
enumerate_mps Exhaustively enumerate minimal pathways (MPs) and
|
|
11
|
+
minimal cut sets (MCSs) in a reaction subset using
|
|
12
|
+
direct or iterative methods.
|
|
13
|
+
sample_mps Stochastically sample minimal pathways from the MP
|
|
14
|
+
space of a reaction subset (always iterative;
|
|
15
|
+
supports weighted and adaptive sampling). Returns
|
|
16
|
+
(mps, acceptance), the unique/total acceptance rate.
|
|
17
|
+
preprocess_model Preprocess a model and subset for MP search:
|
|
18
|
+
fastercc removal, optional FVA tightening or
|
|
19
|
+
compression, MFI, and a preprocessed-to-original
|
|
20
|
+
reaction-ID mapping.
|
|
21
|
+
rerun Reconstruct and re-invoke a recorded find-MP run
|
|
22
|
+
from an exported run bundle (see the ``export``
|
|
23
|
+
option); pure record-and-replay, so the replayed
|
|
24
|
+
call behaves identically to the original.
|
|
25
|
+
validate_mps Validate that each MP is feasible and support-
|
|
26
|
+
minimal against a prepared model (returns one
|
|
27
|
+
bool per MP).
|
|
28
|
+
validate_solutions Validate that every passed flux vector is feasible
|
|
29
|
+
in a model (bounds + constraints); raises on the
|
|
30
|
+
first infeasible solution.
|
|
31
|
+
translate_mps Translate enumerated/sampled MPs from the
|
|
32
|
+
preprocessed namespace back to the original
|
|
33
|
+
(union expansion per member).
|
|
34
|
+
translate_mcs Translate enumerated MCSs from the
|
|
35
|
+
preprocessed namespace back to the original
|
|
36
|
+
(Cartesian product + antichain filter).
|
|
37
|
+
create_lp Build a Gurobi LP from a COBRApy model.
|
|
38
|
+
copy_model Copy a Gurobi model preserving solver parameters.
|
|
39
|
+
set_bounds Apply ``{rxn_id: (lb, ub)}`` bounds to a Gurobi LP
|
|
40
|
+
in place (e.g. FVA-tight bounds from ``fva``).
|
|
41
|
+
load_cobra_model Load a metabolic model (SBML/JSON/MATLAB).
|
|
42
|
+
load_cobra_models Load one or more models from files or a directory.
|
|
43
|
+
load_set, save_set Read/write a set of reaction IDs from/to text.
|
|
44
|
+
load_sets, save_sets Read/write a collection of sets of IDs from/to CSV.
|
|
45
|
+
load_bounds, Read/write per-reaction flux bounds from/to CSV.
|
|
46
|
+
save_bounds
|
|
47
|
+
load_mapping, Read/write a preprocessed-to-original reaction
|
|
48
|
+
save_mapping mapping (``{new_id: {orig_id: coef}}``) from/to CSV.
|
|
49
|
+
load_weights Read per-reaction sampling weights from a
|
|
50
|
+
``reaction_id,weight`` CSV.
|
|
51
|
+
make_forward_ Split reversible reactions into forward and reverse
|
|
52
|
+
irreversible half-reactions with non-negative bounds; returns
|
|
53
|
+
the updated subset and a {new_id: {orig_id: coef}}
|
|
54
|
+
map.
|
|
55
|
+
fva Flux variability analysis on a Gurobi LP model.
|
|
56
|
+
compress Lossless FVA-based network compression: merge
|
|
57
|
+
flux-coupled reactions into single variables.
|
|
58
|
+
fastcc Identify flux-consistent reactions (fast version).
|
|
59
|
+
fastercc Identify flux-consistent reactions (faster).
|
|
60
|
+
|
|
61
|
+
To apply FVA-tight bounds without compressing, run :func:`mptool.fva` and pass
|
|
62
|
+
the result to :func:`mptool.set_bounds`.
|
|
63
|
+
|
|
64
|
+
.. note::
|
|
65
|
+
The name ``mptool.fva`` is the *function* above, not the ``mptool.fva``
|
|
66
|
+
submodule it is defined in — this module rebinds the attribute on import.
|
|
67
|
+
So ``mptool.fva.compress`` does not resolve; use :func:`mptool.compress`,
|
|
68
|
+
or ``from mptool.fva import compress`` to reach it through the submodule.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
|
|
72
|
+
|
|
73
|
+
from .io import (load_cobra_model, load_cobra_models,
|
|
74
|
+
load_set, save_set, load_sets, save_sets,
|
|
75
|
+
load_bounds, save_bounds, load_mapping, save_mapping,
|
|
76
|
+
load_weights)
|
|
77
|
+
from .mapping import translate_mcs, translate_mps
|
|
78
|
+
from .model import create_lp, copy_model, make_forward_irreversible, set_bounds
|
|
79
|
+
from .fva import compress, fva
|
|
80
|
+
from .consistency import fastcc, fastercc
|
|
81
|
+
from .preprocess import preprocess_model
|
|
82
|
+
from .mps import enumerate_mps, sample_mps
|
|
83
|
+
from .reproduce import rerun
|
|
84
|
+
from .validate import validate_mps, validate_solutions
|
|
85
|
+
|
|
86
|
+
# Single source of truth is ``version`` in setup.cfg, read here from the
|
|
87
|
+
# installed distribution metadata.
|
|
88
|
+
try:
|
|
89
|
+
__version__ = _pkg_version('mptool')
|
|
90
|
+
except PackageNotFoundError: # pragma: no cover - package always installed
|
|
91
|
+
__version__ = 'unknown' # source tree that was never installed
|
|
92
|
+
|
|
93
|
+
__all__ = [
|
|
94
|
+
'enumerate_mps', 'sample_mps', 'preprocess_model',
|
|
95
|
+
'rerun',
|
|
96
|
+
'create_lp', 'copy_model', 'set_bounds',
|
|
97
|
+
'load_cobra_model', 'load_cobra_models',
|
|
98
|
+
'load_set', 'save_set', 'load_sets', 'save_sets',
|
|
99
|
+
'load_bounds', 'save_bounds',
|
|
100
|
+
'load_mapping', 'save_mapping',
|
|
101
|
+
'load_weights',
|
|
102
|
+
'make_forward_irreversible', 'fva', 'compress',
|
|
103
|
+
'fastcc', 'fastercc',
|
|
104
|
+
'validate_mps', 'validate_solutions',
|
|
105
|
+
'translate_mps', 'translate_mcs',
|
|
106
|
+
'__version__',
|
|
107
|
+
]
|
mptool/__main__.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"""Command-line interface for ``python -m mptool``.
|
|
2
|
+
|
|
3
|
+
Exposes two subcommands:
|
|
4
|
+
|
|
5
|
+
* ``python -m mptool enumerate <model> [options]`` —
|
|
6
|
+
:func:`mptool.enumerate_mps` (exhaustive enumeration of MPs and MCSs).
|
|
7
|
+
* ``python -m mptool sample <model> [options]`` —
|
|
8
|
+
:func:`mptool.sample_mps` (stochastic sampling of MPs).
|
|
9
|
+
|
|
10
|
+
Options common to both live on a shared parent parser so they cannot drift.
|
|
11
|
+
The Python-only parameters ``requirements`` (a callback) and ``preprocess`` /
|
|
12
|
+
``return_mapping`` are deliberately not exposed; with compression active the
|
|
13
|
+
preprocessed→original reaction mapping reaches CLI users as ``mapping.csv`` in
|
|
14
|
+
the ``--export`` bundle.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from importlib.metadata import version as _pkg_version
|
|
21
|
+
|
|
22
|
+
from mptool import (enumerate_mps, load_bounds, load_cobra_model, load_set,
|
|
23
|
+
load_weights, sample_mps)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _int_at_least(minimum):
|
|
27
|
+
"""Return an argparse ``type`` parsing an int that must be >= ``minimum``."""
|
|
28
|
+
def parse(value):
|
|
29
|
+
ivalue = int(value)
|
|
30
|
+
if ivalue < minimum:
|
|
31
|
+
raise argparse.ArgumentTypeError(
|
|
32
|
+
f'must be an integer >= {minimum}; got {ivalue}')
|
|
33
|
+
return ivalue
|
|
34
|
+
return parse
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _float_at_least(minimum):
|
|
38
|
+
"""Return an argparse ``type`` parsing a float that must be >= ``minimum``.
|
|
39
|
+
|
|
40
|
+
Used where 0 is a meaningful value (``--max-t 0`` and ``--conv-tol 0``
|
|
41
|
+
both mean "off"), so the bound is inclusive.
|
|
42
|
+
"""
|
|
43
|
+
def parse(value):
|
|
44
|
+
fvalue = float(value)
|
|
45
|
+
if not fvalue >= minimum: # also rejects nan
|
|
46
|
+
raise argparse.ArgumentTypeError(
|
|
47
|
+
f'must be a number >= {minimum}; got {value}')
|
|
48
|
+
return fvalue
|
|
49
|
+
return parse
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _float_above(bound):
|
|
53
|
+
"""Return an argparse ``type`` parsing a float that must be > ``bound``.
|
|
54
|
+
|
|
55
|
+
Used for tolerances and magnitudes that are inoperative at zero, so the
|
|
56
|
+
CLI rejects them at parse time rather than surfacing a traceback from
|
|
57
|
+
deep inside the search.
|
|
58
|
+
"""
|
|
59
|
+
def parse(value):
|
|
60
|
+
fvalue = float(value)
|
|
61
|
+
if not fvalue > bound: # also rejects nan
|
|
62
|
+
raise argparse.ArgumentTypeError(
|
|
63
|
+
f'must be a number > {bound}; got {value}')
|
|
64
|
+
return fvalue
|
|
65
|
+
return parse
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _build_common_parser():
|
|
69
|
+
"""Build the shared parent parser for options common to both subcommands."""
|
|
70
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
71
|
+
|
|
72
|
+
common.add_argument(
|
|
73
|
+
'model',
|
|
74
|
+
help='Path to model file (SBML, JSON, or MATLAB format).',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
common.add_argument(
|
|
78
|
+
'--subset',
|
|
79
|
+
metavar='FILE',
|
|
80
|
+
default=None,
|
|
81
|
+
help=(
|
|
82
|
+
'Path to a text file with one reaction ID per line. '
|
|
83
|
+
'Reactions in this file are the candidates for MPs. '
|
|
84
|
+
'If omitted, all model reactions are used.'
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
common.add_argument(
|
|
88
|
+
'--bounds',
|
|
89
|
+
metavar='FILE',
|
|
90
|
+
default=None,
|
|
91
|
+
help=(
|
|
92
|
+
'Path to a CSV bounds file (reaction_id,lb,ub per line). '
|
|
93
|
+
'Overrides model bounds for the listed reactions.'
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
common.add_argument(
|
|
98
|
+
'--chunks',
|
|
99
|
+
type=_int_at_least(2),
|
|
100
|
+
default=None,
|
|
101
|
+
dest='chunks',
|
|
102
|
+
metavar='N',
|
|
103
|
+
help=(
|
|
104
|
+
'Per-iteration deletion strategy. Omit for single-reaction '
|
|
105
|
+
'deletion (default); pass an integer >= 2 for chunked deletion '
|
|
106
|
+
'with that many splits per failed chunk.'
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
common.add_argument(
|
|
111
|
+
'--tighten', action=argparse.BooleanOptionalAction, default=True,
|
|
112
|
+
help='Tighten bounds via flux variability analysis.',
|
|
113
|
+
)
|
|
114
|
+
common.add_argument(
|
|
115
|
+
'--compress', action=argparse.BooleanOptionalAction, default=True,
|
|
116
|
+
help=(
|
|
117
|
+
'Losslessly compress the model via FVA-based flux coupling '
|
|
118
|
+
'before the search. Requires --tighten.'
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
common.add_argument(
|
|
122
|
+
'--verbose', action=argparse.BooleanOptionalAction, default=True,
|
|
123
|
+
help='Print per-MP progress output.',
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
common.add_argument(
|
|
127
|
+
'--export', action='store_true', default=False,
|
|
128
|
+
help='Write a run bundle: log, model, solver settings, bounds, '
|
|
129
|
+
'manifest, plus MPs, MCSs and mapping when the run produces '
|
|
130
|
+
'them. The subset is recorded in the manifest, not as a '
|
|
131
|
+
'separate file.',
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
common.add_argument(
|
|
135
|
+
'--flux-tol',
|
|
136
|
+
type=_float_above(0.0),
|
|
137
|
+
default=1e-9,
|
|
138
|
+
metavar='FLUX_TOL',
|
|
139
|
+
help='Threshold below which a flux value or bound is considered '
|
|
140
|
+
'zero; also sets the Gurobi solver tolerances.',
|
|
141
|
+
)
|
|
142
|
+
common.add_argument(
|
|
143
|
+
'--merge-tol',
|
|
144
|
+
type=_float_above(0.0),
|
|
145
|
+
default=1e-6,
|
|
146
|
+
metavar='MERGE_TOL',
|
|
147
|
+
help='Numerical-zero threshold on cluster-cancellation residuals '
|
|
148
|
+
'in compression (only used with --compress). Unlike '
|
|
149
|
+
'--flux-tol, the risky direction is tighter: below the 1e-6 '
|
|
150
|
+
'default the residuals can make the compressed model '
|
|
151
|
+
'infeasible, and a warning is emitted.',
|
|
152
|
+
)
|
|
153
|
+
common.add_argument(
|
|
154
|
+
'--inf',
|
|
155
|
+
type=_float_above(0.0),
|
|
156
|
+
default=1000.0,
|
|
157
|
+
metavar='INF',
|
|
158
|
+
help='Largest allowed flux bound (absolute value).',
|
|
159
|
+
)
|
|
160
|
+
common.add_argument(
|
|
161
|
+
'--threads',
|
|
162
|
+
type=_int_at_least(0),
|
|
163
|
+
default=0,
|
|
164
|
+
metavar='N',
|
|
165
|
+
help='Number of Gurobi solver threads (0 = all available cores).',
|
|
166
|
+
)
|
|
167
|
+
common.add_argument(
|
|
168
|
+
'--max-t',
|
|
169
|
+
dest='max_t',
|
|
170
|
+
type=_float_at_least(0.0),
|
|
171
|
+
default=0.0,
|
|
172
|
+
metavar='SECONDS',
|
|
173
|
+
help='Time limit in seconds for the MP search (0 = no limit). '
|
|
174
|
+
'Preprocessing runs before the clock starts, so total runtime '
|
|
175
|
+
'is preprocessing plus this budget.',
|
|
176
|
+
)
|
|
177
|
+
common.add_argument(
|
|
178
|
+
'--output-dir',
|
|
179
|
+
dest='output_dir',
|
|
180
|
+
default='',
|
|
181
|
+
metavar='DIR',
|
|
182
|
+
help='Directory for exported files. Defaults to the current directory.',
|
|
183
|
+
)
|
|
184
|
+
common.add_argument(
|
|
185
|
+
'--seed',
|
|
186
|
+
type=_int_at_least(0),
|
|
187
|
+
default=None,
|
|
188
|
+
metavar='N',
|
|
189
|
+
help=(
|
|
190
|
+
'Seed for the random number generator. Two runs with the same '
|
|
191
|
+
'seed and identical other parameters produce identical results.'
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return common
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _build_parser():
|
|
199
|
+
"""Build and return the argument parser for the mptool CLI."""
|
|
200
|
+
parser = argparse.ArgumentParser(
|
|
201
|
+
prog='python -m mptool',
|
|
202
|
+
description=(
|
|
203
|
+
'Enumerate or sample minimal pathways (MPs) in a metabolic model.'
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
parser.add_argument(
|
|
207
|
+
'--version', action='version',
|
|
208
|
+
version=f'%(prog)s {_pkg_version("mptool")}',
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
common = _build_common_parser()
|
|
212
|
+
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
213
|
+
|
|
214
|
+
# ── enumerate ────────────────────────────────────────────────────────────
|
|
215
|
+
p_enum = subparsers.add_parser(
|
|
216
|
+
'enumerate', parents=[common],
|
|
217
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
218
|
+
help='Exhaustively enumerate MPs (and MCSs) in the model.',
|
|
219
|
+
description=(
|
|
220
|
+
'Exhaustively enumerate minimal pathways (MPs), and minimal cut '
|
|
221
|
+
'sets (MCSs) with the iterative method, in a metabolic model.'
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
p_enum.add_argument(
|
|
225
|
+
'--method',
|
|
226
|
+
default='iterative',
|
|
227
|
+
choices=('iterative', 'direct'),
|
|
228
|
+
help=(
|
|
229
|
+
'Enumeration method. '
|
|
230
|
+
"'direct' requires --no-graph and is not compatible with "
|
|
231
|
+
'--chunks; it also finds no MCSs.'
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
p_enum.add_argument(
|
|
235
|
+
'--mcs-method',
|
|
236
|
+
dest='mcs_method',
|
|
237
|
+
default='bip',
|
|
238
|
+
choices=('bip', 'berge'),
|
|
239
|
+
help=(
|
|
240
|
+
"Algorithm for minimal cut candidates (iterative only). "
|
|
241
|
+
"'bip' uses a Gurobi BIP; 'berge' uses an alternative incremental "
|
|
242
|
+
"combinatorial enumerator."
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
p_enum.add_argument(
|
|
246
|
+
'--graph', action=argparse.BooleanOptionalAction, default=True,
|
|
247
|
+
help=(
|
|
248
|
+
'Reaction co-occurrence graph acceleration (iterative method '
|
|
249
|
+
'only; pass --no-graph with --method direct).'
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
p_enum.add_argument(
|
|
253
|
+
'--max-mps',
|
|
254
|
+
dest='max_mps',
|
|
255
|
+
type=_int_at_least(0),
|
|
256
|
+
default=0,
|
|
257
|
+
metavar='N',
|
|
258
|
+
help='Maximum number of MPs to find (0 = no limit).',
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# ── sample ───────────────────────────────────────────────────────────────
|
|
262
|
+
p_sample = subparsers.add_parser(
|
|
263
|
+
'sample', parents=[common],
|
|
264
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
265
|
+
help='Stochastically sample MPs from the model.',
|
|
266
|
+
description=(
|
|
267
|
+
'Stochastically sample minimal pathways (MPs) from a metabolic '
|
|
268
|
+
'model. Requires at least one of --n-samples or --max-t.'
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
p_sample.add_argument(
|
|
272
|
+
'--n-samples',
|
|
273
|
+
dest='n_samples',
|
|
274
|
+
type=_int_at_least(0),
|
|
275
|
+
default=0,
|
|
276
|
+
metavar='N',
|
|
277
|
+
help='Target number of MPs to sample (0 = no MP limit; then --max-t '
|
|
278
|
+
'is required).',
|
|
279
|
+
)
|
|
280
|
+
p_sample.add_argument(
|
|
281
|
+
'--weights',
|
|
282
|
+
metavar='FILE',
|
|
283
|
+
default=None,
|
|
284
|
+
help=(
|
|
285
|
+
'Path to a CSV weights file (reaction_id,weight per line) biasing '
|
|
286
|
+
'the random ordering. Higher weight -> reaction more likely to be '
|
|
287
|
+
'retained in sampled MPs.'
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
p_sample.add_argument(
|
|
291
|
+
'--adaptive', action='store_true', default=False,
|
|
292
|
+
help='Update weights adaptively after each sample to reduce length '
|
|
293
|
+
'bias.',
|
|
294
|
+
)
|
|
295
|
+
p_sample.add_argument(
|
|
296
|
+
'--conv-tol',
|
|
297
|
+
dest='conv_tol',
|
|
298
|
+
type=_float_at_least(0.0),
|
|
299
|
+
default=0.0,
|
|
300
|
+
metavar='CONV_TOL',
|
|
301
|
+
help='Early-stopping threshold for adaptive sampling (0 = disabled).',
|
|
302
|
+
)
|
|
303
|
+
p_sample.add_argument(
|
|
304
|
+
'--conv-window',
|
|
305
|
+
dest='conv_window',
|
|
306
|
+
type=_int_at_least(1),
|
|
307
|
+
default=10,
|
|
308
|
+
metavar='N',
|
|
309
|
+
help='Rolling window for the adaptive convergence check.',
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return parser
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def main(argv=None):
|
|
316
|
+
"""Entry point for ``python -m mptool``.
|
|
317
|
+
|
|
318
|
+
Parses ``argv`` (or ``sys.argv[1:]`` when None) and dispatches to
|
|
319
|
+
:func:`enumerate_mps` or :func:`sample_mps` according to the chosen
|
|
320
|
+
subcommand.
|
|
321
|
+
"""
|
|
322
|
+
parser = _build_parser()
|
|
323
|
+
args = parser.parse_args(argv)
|
|
324
|
+
|
|
325
|
+
if args.subset is not None and not os.path.isfile(args.subset):
|
|
326
|
+
parser.error(
|
|
327
|
+
f'--subset: file not found or not a regular file: {args.subset}'
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
if args.bounds is not None and not os.path.isfile(args.bounds):
|
|
331
|
+
parser.error(
|
|
332
|
+
f'--bounds: file not found or not a regular file: {args.bounds}'
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Compression always tightens bounds; preprocess_model rejects the
|
|
336
|
+
# combination. Catch it here as a clean CLI error before any model work.
|
|
337
|
+
if args.compress and not args.tighten:
|
|
338
|
+
parser.error(
|
|
339
|
+
'--compress requires --tighten (compression always tightens bounds)'
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# enumerate-specific validation, fail-fast before any model work (mirrors
|
|
343
|
+
# the checks above and enumerate_mps' own guards). --graph defaults to
|
|
344
|
+
# True, so `--method direct` on its own would otherwise reach
|
|
345
|
+
# enumerate_mps and die with an unhandled ValueError traceback; --chunks
|
|
346
|
+
# is the same incompatibility, reached only when the user passes it.
|
|
347
|
+
if args.command == 'enumerate' and args.method == 'direct':
|
|
348
|
+
if args.graph:
|
|
349
|
+
parser.error(
|
|
350
|
+
'--method direct is not compatible with --graph: the '
|
|
351
|
+
'co-occurrence graph accelerates the iterative method only. '
|
|
352
|
+
'Add --no-graph, e.g. `python -m mptool enumerate MODEL '
|
|
353
|
+
'--method direct --no-graph`'
|
|
354
|
+
)
|
|
355
|
+
if args.chunks is not None:
|
|
356
|
+
parser.error(
|
|
357
|
+
'--method direct is not compatible with --chunks: chunked '
|
|
358
|
+
'deletion is an iterative-method strategy. Drop --chunks, or '
|
|
359
|
+
'use --method iterative'
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# sample-specific validation, fail-fast before any model work (mirrors the
|
|
363
|
+
# subset/bounds existence checks above and sample_mps' own guard).
|
|
364
|
+
if args.command == 'sample':
|
|
365
|
+
if not (args.n_samples or args.max_t):
|
|
366
|
+
parser.error(
|
|
367
|
+
'sample requires at least one of --n-samples or --max-t '
|
|
368
|
+
'to terminate sampling'
|
|
369
|
+
)
|
|
370
|
+
if args.weights is not None and not os.path.isfile(args.weights):
|
|
371
|
+
parser.error(
|
|
372
|
+
f'--weights: file not found or not a regular file: '
|
|
373
|
+
f'{args.weights}'
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
cobra_model = load_cobra_model(args.model)
|
|
377
|
+
bounds = load_bounds(args.bounds) if args.bounds is not None else {}
|
|
378
|
+
subset = load_set(args.subset) if args.subset is not None else None
|
|
379
|
+
|
|
380
|
+
preprocess_kwargs = {
|
|
381
|
+
'bounds': bounds,
|
|
382
|
+
'tighten': args.tighten,
|
|
383
|
+
'compress': args.compress,
|
|
384
|
+
'merge_tol': args.merge_tol,
|
|
385
|
+
'inf': args.inf,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if args.command == 'enumerate':
|
|
389
|
+
enumerate_mps(
|
|
390
|
+
cobra_model,
|
|
391
|
+
subset=subset,
|
|
392
|
+
method=args.method,
|
|
393
|
+
chunks=args.chunks,
|
|
394
|
+
graph=args.graph,
|
|
395
|
+
mcs_method=args.mcs_method,
|
|
396
|
+
threads=args.threads,
|
|
397
|
+
max_mps=args.max_mps,
|
|
398
|
+
max_t=args.max_t,
|
|
399
|
+
seed=args.seed,
|
|
400
|
+
flux_tol=args.flux_tol,
|
|
401
|
+
verbose=args.verbose,
|
|
402
|
+
export=args.export,
|
|
403
|
+
output_dir=args.output_dir,
|
|
404
|
+
preprocess_kwargs=preprocess_kwargs,
|
|
405
|
+
)
|
|
406
|
+
else:
|
|
407
|
+
weights = load_weights(args.weights) if args.weights is not None \
|
|
408
|
+
else None
|
|
409
|
+
sample_mps(
|
|
410
|
+
cobra_model,
|
|
411
|
+
subset=subset,
|
|
412
|
+
n_samples=args.n_samples,
|
|
413
|
+
weights=weights,
|
|
414
|
+
adaptive=args.adaptive,
|
|
415
|
+
conv_tol=args.conv_tol,
|
|
416
|
+
conv_window=args.conv_window,
|
|
417
|
+
chunks=args.chunks,
|
|
418
|
+
threads=args.threads,
|
|
419
|
+
max_t=args.max_t,
|
|
420
|
+
seed=args.seed,
|
|
421
|
+
flux_tol=args.flux_tol,
|
|
422
|
+
verbose=args.verbose,
|
|
423
|
+
export=args.export,
|
|
424
|
+
output_dir=args.output_dir,
|
|
425
|
+
preprocess_kwargs=preprocess_kwargs,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
if __name__ == '__main__':
|
|
430
|
+
sys.exit(main())
|