equilibrator-pathway-core 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.
Files changed (27) hide show
  1. equilibrator_pathway_core-0.1.0/LICENSE +24 -0
  2. equilibrator_pathway_core-0.1.0/PKG-INFO +107 -0
  3. equilibrator_pathway_core-0.1.0/README.md +83 -0
  4. equilibrator_pathway_core-0.1.0/pyproject.toml +71 -0
  5. equilibrator_pathway_core-0.1.0/setup.cfg +4 -0
  6. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/__init__.py +41 -0
  7. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/configuration.py +293 -0
  8. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/constants.py +52 -0
  9. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/ecm.py +586 -0
  10. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/ecm_model.py +205 -0
  11. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/errors.py +62 -0
  12. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/mdf.py +248 -0
  13. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/model.py +709 -0
  14. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/solution.py +340 -0
  15. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core/units.py +224 -0
  16. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core.egg-info/PKG-INFO +107 -0
  17. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core.egg-info/SOURCES.txt +25 -0
  18. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core.egg-info/dependency_links.txt +1 -0
  19. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core.egg-info/requires.txt +4 -0
  20. equilibrator_pathway_core-0.1.0/src/equilibrator_pathway_core.egg-info/top_level.txt +1 -0
  21. equilibrator_pathway_core-0.1.0/tests/test_configuration.py +132 -0
  22. equilibrator_pathway_core-0.1.0/tests/test_constants.py +49 -0
  23. equilibrator_pathway_core-0.1.0/tests/test_ecm.py +110 -0
  24. equilibrator_pathway_core-0.1.0/tests/test_ecm_model.py +79 -0
  25. equilibrator_pathway_core-0.1.0/tests/test_isolation.py +104 -0
  26. equilibrator_pathway_core-0.1.0/tests/test_mdf.py +136 -0
  27. equilibrator_pathway_core-0.1.0/tests/test_model.py +161 -0
@@ -0,0 +1,24 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Weizmann Institute of Science
4
+ Copyright (c) 2018-2020 Institute for Molecular Systems Biology, ETH Zurich
5
+ Copyright (c) 2018-2020 Novo Nordisk Foundation Center for Biosustainability,
6
+ Technical University of Denmark
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: equilibrator-pathway-core
3
+ Version: 0.1.0
4
+ Summary: MDF and enzyme cost minimization for fully specified pathway models, with no compound database
5
+ Author-email: Elad Noor <elad.noor@weizmann.ac.il>
6
+ License-Expression: MIT
7
+ Project-URL: repository, https://gitlab.com/equilibrator/equilibrator-pathway-core/
8
+ Keywords: eQuilibrator,pathway analysis,max-min driving force,enzyme cost minimization
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
12
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
13
+ Classifier: Natural Language :: English
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: numpy>=1.26
20
+ Requires-Dist: scipy>=1.13
21
+ Requires-Dist: cvxpy>=1.6
22
+ Requires-Dist: sbtabpy>=1.1.1
23
+ Dynamic: license-file
24
+
25
+ # equilibrator-pathway-core
26
+
27
+ Max-min driving force (MDF) and enzyme cost minimization (ECM) for **fully
28
+ specified** pathway models, with no compound database.
29
+
30
+ This is the lower layer of
31
+ [equilibrator-pathway](https://gitlab.com/equilibrator/equilibrator-pathway).
32
+ It analyses model SBtab files that already carry everything the optimisation
33
+ needs -- stoichiometry, concentration bounds, fluxes and standard Gibbs energies
34
+ -- and it never predicts or looks anything up. That is what lets it install in
35
+ a few megabytes of Python on top of numpy, including in the browser via
36
+ [Pyodide](https://pyodide.org).
37
+
38
+ | | this package | equilibrator-pathway |
39
+ | --- | --- | --- |
40
+ | MDF, ECM on a model SBtab | yes | yes (re-exported) |
41
+ | network SBtabs, formula search, identifier resolution | -- | yes |
42
+ | dG'0 prediction (Component Contribution) | -- | yes |
43
+ | dG'0 uncertainty, MDMC | -- | yes |
44
+ | pint `Q_` public API | -- | yes |
45
+ | compound database, sqlalchemy, pint | never | yes |
46
+
47
+ ## Units
48
+
49
+ Everything here works on plain floats in one canonical set of units -- M,
50
+ kJ/mol, K, 1/s, Da -- documented in `equilibrator_pathway_core.constants`.
51
+ Units are attached at the boundary, by equilibrator-pathway's pint adapter or by
52
+ the browser client's string parser.
53
+
54
+ ## Staying small
55
+
56
+ Importing this package must not pull in the equilibrator database stack. That is
57
+ tested directly, in a fresh interpreter, by `tests/test_isolation.py`, which
58
+ fails if `equilibrator_api`, `equilibrator_cache`, `component_contribution`,
59
+ `sqlalchemy`, `pint` or `matplotlib` appear in `sys.modules`. A dependency added
60
+ to `pyproject.toml` without the code that needs it is equally unwelcome.
61
+
62
+ ## Status
63
+
64
+ In place:
65
+
66
+ | module | what | needs sbtabpy |
67
+ | --- | --- | --- |
68
+ | `constants.py` | R, T, standard concentrations | -- |
69
+ | `ecm.py` | the enzyme cost function and ECM | -- |
70
+ | `mdf.py` | the MDF linear program | -- |
71
+ | `errors.py` | `ModelError`, `SolverFailure`, `ConfigurationError` | -- |
72
+ | `units.py` | unit strings to canonical floats | -- |
73
+ | `model.py` | model SBtab to arrays: S, bounds, fluxes, dG'0, water | yes |
74
+ | `ecm_model.py` | an ECM model's Parameter table | yes |
75
+ | `solution.py` | result tables and the SBtab report | yes |
76
+ | `configuration.py` | the editable Configuration options, their schema, and applying edits | yes |
77
+
78
+ Both equilibrator-pathway and the browser client run MDF and ECM through this
79
+ package; the browser also reads models (`model.load_model`), edits their
80
+ Configuration (`configuration.py`) and writes reports with it.
81
+
82
+ The SBtab modules use sbtabpy directly, and need sbtabpy **1.1.1 or later**.
83
+ 1.1.0 was the first release with no required dependencies (earlier ones
84
+ declared pandas, pyarrow, python-libsbml and openpyxl, which its parser never
85
+ imports); 1.1.1 added the dictionary rows, attribute defaults and pandas-free
86
+ `SBtabTable.from_rows` that replaced the core's own adapter module.
87
+ `tests/test_isolation.py` still forbids all four: nothing installs them by
88
+ default now, but the test is what guarantees the core never imports them, and
89
+ so stays loadable in the browser.
90
+
91
+ The extraction plan is in
92
+ `enzyme-cost-minimization-webapp/CORE_EXTRACTION_PLAN.txt`. Correctness is held
93
+ to the golden results frozen in that repository (`tests/golden/`), which record
94
+ what equilibrator-pathway and the browser port each produced while they were
95
+ still independent implementations. After the move, both consumers reproduce
96
+ every one of those 482 numbers **bit for bit**, not merely within the checker's
97
+ 1e-6 tolerance.
98
+
99
+ Where the two replaced copies disagreed, see the docstring of `ecm.py` for
100
+ which behaviour was kept and why. In short: the port's `get_volumes` (the
101
+ server's zeroed the last metabolite when a model had no water), and working
102
+ versions of `get_fluxes` and `is_feasible`, which raised on every call in
103
+ equilibrator-pathway without any test noticing.
104
+
105
+ ```sh
106
+ PYTHONPATH=src pytest tests/
107
+ ```
@@ -0,0 +1,83 @@
1
+ # equilibrator-pathway-core
2
+
3
+ Max-min driving force (MDF) and enzyme cost minimization (ECM) for **fully
4
+ specified** pathway models, with no compound database.
5
+
6
+ This is the lower layer of
7
+ [equilibrator-pathway](https://gitlab.com/equilibrator/equilibrator-pathway).
8
+ It analyses model SBtab files that already carry everything the optimisation
9
+ needs -- stoichiometry, concentration bounds, fluxes and standard Gibbs energies
10
+ -- and it never predicts or looks anything up. That is what lets it install in
11
+ a few megabytes of Python on top of numpy, including in the browser via
12
+ [Pyodide](https://pyodide.org).
13
+
14
+ | | this package | equilibrator-pathway |
15
+ | --- | --- | --- |
16
+ | MDF, ECM on a model SBtab | yes | yes (re-exported) |
17
+ | network SBtabs, formula search, identifier resolution | -- | yes |
18
+ | dG'0 prediction (Component Contribution) | -- | yes |
19
+ | dG'0 uncertainty, MDMC | -- | yes |
20
+ | pint `Q_` public API | -- | yes |
21
+ | compound database, sqlalchemy, pint | never | yes |
22
+
23
+ ## Units
24
+
25
+ Everything here works on plain floats in one canonical set of units -- M,
26
+ kJ/mol, K, 1/s, Da -- documented in `equilibrator_pathway_core.constants`.
27
+ Units are attached at the boundary, by equilibrator-pathway's pint adapter or by
28
+ the browser client's string parser.
29
+
30
+ ## Staying small
31
+
32
+ Importing this package must not pull in the equilibrator database stack. That is
33
+ tested directly, in a fresh interpreter, by `tests/test_isolation.py`, which
34
+ fails if `equilibrator_api`, `equilibrator_cache`, `component_contribution`,
35
+ `sqlalchemy`, `pint` or `matplotlib` appear in `sys.modules`. A dependency added
36
+ to `pyproject.toml` without the code that needs it is equally unwelcome.
37
+
38
+ ## Status
39
+
40
+ In place:
41
+
42
+ | module | what | needs sbtabpy |
43
+ | --- | --- | --- |
44
+ | `constants.py` | R, T, standard concentrations | -- |
45
+ | `ecm.py` | the enzyme cost function and ECM | -- |
46
+ | `mdf.py` | the MDF linear program | -- |
47
+ | `errors.py` | `ModelError`, `SolverFailure`, `ConfigurationError` | -- |
48
+ | `units.py` | unit strings to canonical floats | -- |
49
+ | `model.py` | model SBtab to arrays: S, bounds, fluxes, dG'0, water | yes |
50
+ | `ecm_model.py` | an ECM model's Parameter table | yes |
51
+ | `solution.py` | result tables and the SBtab report | yes |
52
+ | `configuration.py` | the editable Configuration options, their schema, and applying edits | yes |
53
+
54
+ Both equilibrator-pathway and the browser client run MDF and ECM through this
55
+ package; the browser also reads models (`model.load_model`), edits their
56
+ Configuration (`configuration.py`) and writes reports with it.
57
+
58
+ The SBtab modules use sbtabpy directly, and need sbtabpy **1.1.1 or later**.
59
+ 1.1.0 was the first release with no required dependencies (earlier ones
60
+ declared pandas, pyarrow, python-libsbml and openpyxl, which its parser never
61
+ imports); 1.1.1 added the dictionary rows, attribute defaults and pandas-free
62
+ `SBtabTable.from_rows` that replaced the core's own adapter module.
63
+ `tests/test_isolation.py` still forbids all four: nothing installs them by
64
+ default now, but the test is what guarantees the core never imports them, and
65
+ so stays loadable in the browser.
66
+
67
+ The extraction plan is in
68
+ `enzyme-cost-minimization-webapp/CORE_EXTRACTION_PLAN.txt`. Correctness is held
69
+ to the golden results frozen in that repository (`tests/golden/`), which record
70
+ what equilibrator-pathway and the browser port each produced while they were
71
+ still independent implementations. After the move, both consumers reproduce
72
+ every one of those 482 numbers **bit for bit**, not merely within the checker's
73
+ 1e-6 tolerance.
74
+
75
+ Where the two replaced copies disagreed, see the docstring of `ecm.py` for
76
+ which behaviour was kept and why. In short: the port's `get_volumes` (the
77
+ server's zeroed the last metabolite when a model had no water), and working
78
+ versions of `get_fluxes` and `is_feasible`, which raised on every call in
79
+ equilibrator-pathway without any test noticing.
80
+
81
+ ```sh
82
+ PYTHONPATH=src pytest tests/
83
+ ```
@@ -0,0 +1,71 @@
1
+ [project]
2
+ name = "equilibrator-pathway-core"
3
+ description = "MDF and enzyme cost minimization for fully specified pathway models, with no compound database"
4
+ readme = {file = "README.md", content-type = "text/markdown"}
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ authors = [
8
+ {name = "Elad Noor", email = "elad.noor@weizmann.ac.il"}
9
+ ]
10
+ keywords = [
11
+ "eQuilibrator",
12
+ "pathway analysis",
13
+ "max-min driving force",
14
+ "enzyme cost minimization",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Science/Research",
19
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
20
+ "Topic :: Scientific/Engineering :: Chemistry",
21
+ "Natural Language :: English",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ ]
25
+ requires-python = ">=3.11"
26
+ version = "0.1.0"
27
+
28
+ # Deliberately short, and it must stay that way: this distribution exists so
29
+ # that a pathway analysis can be installed -- including in Pyodide -- without
30
+ # the compound database, the Component Contribution parameters, sqlalchemy or
31
+ # pint. tests/test_isolation.py enforces it on every run.
32
+ #
33
+ # Add a dependency only when a module that genuinely needs it moves in, never
34
+ # speculatively.
35
+ #
36
+ # The floors are what the browser actually runs (Pyodide 0.28: cvxpy-base 1.6.3,
37
+ # numpy 2.2.5, scipy 1.14.1), not equilibrator-pathway's tighter pins -- this
38
+ # exact code is exercised there, so they are known to work. In Pyodide the
39
+ # wheel is installed with deps=False and these go unenforced; the worker loads
40
+ # cvxpy-base rather than cvxpy, which has the same import name.
41
+ #
42
+ # sbtabpy is floored at 1.1.1: 1.1.0 was the first release with no required
43
+ # dependencies (earlier ones declared pandas, pyarrow, python-libsbml and
44
+ # openpyxl, none of which its SBtab parser ever imports), and 1.1.1 added the
45
+ # table API the core reads models and writes reports with (`to_dict_rows`,
46
+ # `column_names`, `get_attribute(..., default=)`, `SBtabTable.from_rows`).
47
+ # Install plain `sbtabpy`, never an extra such as `sbtabpy[pandas]`.
48
+ dependencies = [
49
+ "numpy>=1.26",
50
+ "scipy>=1.13",
51
+ "cvxpy>=1.6",
52
+ "sbtabpy>=1.1.1",
53
+ ]
54
+
55
+ [dependency-groups]
56
+ test = [
57
+ "pytest",
58
+ ]
59
+
60
+ [project.urls]
61
+ repository = "https://gitlab.com/equilibrator/equilibrator-pathway-core/"
62
+
63
+ [build-system]
64
+ requires = ["setuptools>=77", "wheel"]
65
+ build-backend = "setuptools.build_meta"
66
+
67
+ [tool.setuptools.packages.find]
68
+ where = ["src"]
69
+
70
+ [tool.ruff]
71
+ target-version = "py311"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,41 @@
1
+ """Max-min driving force and enzyme cost minimization, without a compound database.
2
+
3
+ This is the database-free lower layer of equilibrator-pathway. It analyses
4
+ *fully specified* pathway models -- SBtab files that already carry the
5
+ stoichiometry, bounds, fluxes and standard Gibbs energies -- and never predicts
6
+ or looks anything up. It is shared by equilibrator-pathway (which adds identifier
7
+ resolution, dG'0 prediction and a pint-based API on top) and by the in-browser
8
+ pathway client.
9
+
10
+ Everything here works on plain floats in the canonical units documented in
11
+ :mod:`equilibrator_pathway_core.constants`.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from .constants import (
17
+ DEFAULT_RT,
18
+ DEFAULT_T,
19
+ PHYSIOLOGICAL_CONCENTRATION,
20
+ STANDARD_CONCENTRATION,
21
+ R,
22
+ )
23
+ from .ecm import ECF_DEFAULTS, EnzymeCostFunction
24
+ from .errors import ModelError, SolverFailure
25
+ from .mdf import MdfResult, mdf_analysis
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ __all__ = [
30
+ "DEFAULT_RT",
31
+ "DEFAULT_T",
32
+ "ECF_DEFAULTS",
33
+ "PHYSIOLOGICAL_CONCENTRATION",
34
+ "STANDARD_CONCENTRATION",
35
+ "EnzymeCostFunction",
36
+ "MdfResult",
37
+ "ModelError",
38
+ "R",
39
+ "SolverFailure",
40
+ "mdf_analysis",
41
+ ]
@@ -0,0 +1,293 @@
1
+ """The editable Configuration table: its schema, and applying edits to it.
2
+
3
+ The web form that lets the user tweak a model's `Configuration` table is built
4
+ from the schema here rather than from a hand-written list in the JavaScript, so
5
+ that the offered choices cannot drift away from what the optimization actually
6
+ accepts (`ECF_DEFAULTS` and the branches of `EnzymeCostFunction`).
7
+
8
+ Three kinds of options:
9
+
10
+ * `select` -- a fixed set of choices (`algorithm`, `denominator`, ...)
11
+ * `number` -- a numeric parameter (`stdev_factor`, `ln_conc_confidence`, ...)
12
+ * `display` -- shown but not editable: the aqueous conditions, because the ΔG'°
13
+ values stored in the model were already transformed for exactly those
14
+ conditions. Changing the pH here would not re-transform them -- that needs
15
+ Component Contribution, i.e. the compound database this app deliberately does
16
+ without.
17
+
18
+ Moved here from the browser client (``pathway_client/configuration.py``), step 4
19
+ of CORE_EXTRACTION_PLAN.txt: the schema is a capability the library lacked, and
20
+ keeping it next to the code that consumes the options is what stops the two
21
+ drifting apart. The notes are user-facing text, written for the browser's form.
22
+ """
23
+
24
+ from sbtab.SBtab import SBtabDocument, SBtabTable
25
+
26
+ from .errors import ConfigurationError
27
+ from .model import read_sbtab
28
+
29
+ __all__ = [
30
+ "DISPLAY",
31
+ "NUMBER",
32
+ "OPTION_SCHEMA",
33
+ "SELECT",
34
+ "ConfigurationError",
35
+ "apply_overrides",
36
+ "describe_configuration",
37
+ ]
38
+
39
+ SELECT = "select"
40
+ NUMBER = "number"
41
+ DISPLAY = "display"
42
+
43
+ # Applies to: which algorithm an option has an effect on ("MDF", "ECM" or
44
+ # "both"), so the form can say so.
45
+ OPTION_SCHEMA = {
46
+ "algorithm": {
47
+ "kind": SELECT,
48
+ "choices": ["MDF", "ECM"],
49
+ "label": "Algorithm",
50
+ "applies_to": "both",
51
+ "note": "Which optimization to run. An ECM model also needs a "
52
+ "'Parameter' table with kcat, KM and molecular masses.",
53
+ },
54
+ "version": {
55
+ "kind": SELECT,
56
+ "choices": ["1", "2", "3", "4"],
57
+ "label": "Enzyme cost function level",
58
+ "applies_to": "ECM",
59
+ "note": "1 = capacity only, 2 = + thermodynamics, 3 = + saturation, "
60
+ "4 = + allosteric regulation.",
61
+ },
62
+ "kcat_source": {
63
+ "kind": SELECT,
64
+ "choices": ["gmean", "fwd"],
65
+ "label": "kcat source",
66
+ "applies_to": "ECM",
67
+ "note": "'gmean' distributes the geometric-mean kcat over both "
68
+ "directions using the Haldane relationship; 'fwd' takes kcat in the "
69
+ "direction of the flux as given.",
70
+ },
71
+ "denominator": {
72
+ "kind": SELECT,
73
+ "choices": ["S", "1S", "SP", "1SP", "CM"],
74
+ "label": "Rate law denominator",
75
+ "applies_to": "ECM",
76
+ "note": "The saturation term of the rate law; 'CM' is the common "
77
+ "modular rate law.",
78
+ },
79
+ "regularization": {
80
+ "kind": SELECT,
81
+ "choices": ["volume"],
82
+ "label": "Regularization",
83
+ "applies_to": "ECM",
84
+ "note": "Only 'volume' is implemented: support for the other options "
85
+ "was dropped when equilibrator-pathway moved to CVXPY.",
86
+ },
87
+ "objective": {
88
+ "kind": SELECT,
89
+ "choices": ["enzyme", "enzyme + metabolite"],
90
+ "label": "Objective",
91
+ "applies_to": "ECM",
92
+ "note": "Minimize the enzyme weight alone, or the enzyme plus "
93
+ "metabolite weight.",
94
+ },
95
+ "ln_conc_confidence": {
96
+ "kind": NUMBER,
97
+ "label": "Log-concentration confidence",
98
+ "applies_to": "both",
99
+ "minimum": 0.0,
100
+ "maximum": 0.999999,
101
+ "note": "The confidence level of the concentration bounds (default "
102
+ "0.95). Lowering it narrows the allowed concentration range.",
103
+ },
104
+ "dg_confidence": {
105
+ "kind": NUMBER,
106
+ "label": "ΔG'° confidence",
107
+ "applies_to": "MDF",
108
+ "minimum": 0.0,
109
+ "maximum": 0.999999,
110
+ "note": "Has no effect on an uploaded model: it widens the ΔG'° "
111
+ "bounds using the uncertainty covariance, which a model SBtab does "
112
+ "not store (dg_sigma is None).",
113
+ },
114
+ "stdev_factor": {
115
+ "kind": NUMBER,
116
+ "label": "Standard deviation factor",
117
+ "applies_to": "both",
118
+ "note": "Currently unused by the optimization -- in "
119
+ "equilibrator-pathway it is still a TODO in ecm_model.py, so changing "
120
+ "it will not change the results.",
121
+ },
122
+ "solver": {
123
+ "kind": DISPLAY,
124
+ "label": "Solver",
125
+ "applies_to": "both",
126
+ "note": "CLARABEL is the only solver available in the browser (it is "
127
+ "also eQuilibrator's default).",
128
+ },
129
+ "p_h": {
130
+ "kind": DISPLAY,
131
+ "label": "pH",
132
+ "applies_to": "both",
133
+ "note": "The ΔG'° values in this model were transformed for these "
134
+ "conditions; re-transforming them needs the compound database.",
135
+ },
136
+ "p_mg": {"kind": DISPLAY, "label": "pMg", "applies_to": "both"},
137
+ "ionic_strength": {
138
+ "kind": DISPLAY,
139
+ "label": "Ionic strength",
140
+ "applies_to": "both",
141
+ },
142
+ "temperature": {"kind": DISPLAY, "label": "Temperature", "applies_to": "both"},
143
+ "e_potential": {
144
+ "kind": DISPLAY,
145
+ "label": "Electrostatic potential",
146
+ "applies_to": "both",
147
+ },
148
+ }
149
+
150
+
151
+ def _configuration_table(document: SBtabDocument) -> SBtabTable:
152
+ table = document.get_sbtab_by_id("Configuration")
153
+ if table is None:
154
+ raise ConfigurationError("Missing 'Configuration' table")
155
+ if "Option" not in table.column_names or "Value" not in table.column_names:
156
+ raise ConfigurationError(
157
+ "The Configuration table must have an Option and a Value column"
158
+ )
159
+ return table
160
+
161
+
162
+ def describe_configuration(text: str, filename: str = "pathway.tsv") -> dict:
163
+ """Describe the Configuration table of a model, for the edit form.
164
+
165
+ Returns the options in the order they appear in the file, each with its
166
+ current value and how it may be edited. Options that are not in the schema
167
+ are reported as `display`, so an unknown option is shown but never silently
168
+ changed.
169
+ """
170
+ document = read_sbtab(text, filename)
171
+ table = _configuration_table(document)
172
+
173
+ options = []
174
+ for row in table.to_dict_rows():
175
+ name = row.get("Option", "").strip()
176
+ if not name:
177
+ continue
178
+ value = row.get("Value", "").strip()
179
+ schema = OPTION_SCHEMA.get(name, {})
180
+ option = {
181
+ "name": name,
182
+ "value": value,
183
+ "kind": schema.get("kind", DISPLAY),
184
+ "label": schema.get("label", name),
185
+ "applies_to": schema.get("applies_to", "both"),
186
+ "note": schema.get("note"),
187
+ "comment": (row.get("Comment") or "").strip(),
188
+ }
189
+ if option["kind"] == SELECT:
190
+ choices = list(schema["choices"])
191
+ # keep whatever the file says as a choice, even when it is not one
192
+ # we know about, so that opening the form cannot alter the model
193
+ if not any(choice.lower() == value.lower() for choice in choices):
194
+ if value:
195
+ choices.append(value)
196
+ else:
197
+ # normalize to the schema's spelling of the current value
198
+ option["value"] = next(
199
+ choice for choice in choices if choice.lower() == value.lower()
200
+ )
201
+ option["choices"] = choices
202
+ elif option["kind"] == NUMBER:
203
+ for key in ("minimum", "maximum"):
204
+ if key in schema:
205
+ option[key] = schema[key]
206
+ options.append(option)
207
+
208
+ return {
209
+ "filename": filename,
210
+ "options": options,
211
+ "algorithm": next(
212
+ (
213
+ option["value"].upper()
214
+ for option in options
215
+ if option["name"] == "algorithm"
216
+ ),
217
+ None,
218
+ ),
219
+ }
220
+
221
+
222
+ def apply_overrides(
223
+ document: SBtabDocument, overrides: dict[str, str] | None
224
+ ) -> list[str]:
225
+ """Apply edited Configuration values to a document, in place.
226
+
227
+ Returns a list of human-readable descriptions of what changed, for the
228
+ result page. Raises `ConfigurationError` if an override names an option
229
+ that is absent or not editable, or gives a value the code would not accept.
230
+ """
231
+ if not overrides:
232
+ return []
233
+
234
+ table = _configuration_table(document)
235
+ # the rows are copies, so edits go back through `change_value`, by position
236
+ # (1-based): the Option column need not come first, which rules out
237
+ # `change_value_by_name`
238
+ value_column = table.column_names.index("Value") + 1
239
+ rows_by_name = {}
240
+ for position, row in enumerate(table.to_dict_rows(), start=1):
241
+ name = row.get("Option", "").strip()
242
+ if name:
243
+ rows_by_name.setdefault(name, (position, row))
244
+
245
+ changes = []
246
+ for name, new_value in overrides.items():
247
+ if name not in rows_by_name:
248
+ raise ConfigurationError(
249
+ f"The Configuration table of this model has no option '{name}'"
250
+ )
251
+
252
+ position, row = rows_by_name[name]
253
+ schema = OPTION_SCHEMA.get(name, {})
254
+ kind = schema.get("kind", DISPLAY)
255
+ new_value = str(new_value).strip()
256
+
257
+ if kind == DISPLAY:
258
+ raise ConfigurationError(f"The option '{name}' cannot be edited")
259
+ if kind == SELECT:
260
+ choices = schema["choices"]
261
+ match = next(
262
+ (choice for choice in choices if choice.lower() == new_value.lower()),
263
+ None,
264
+ )
265
+ if match is None:
266
+ raise ConfigurationError(
267
+ f"'{new_value}' is not a valid value for '{name}' "
268
+ f"(choose one of: {', '.join(choices)})"
269
+ )
270
+ new_value = match
271
+ elif kind == NUMBER:
272
+ try:
273
+ number = float(new_value)
274
+ except ValueError:
275
+ raise ConfigurationError(
276
+ f"The option '{name}' must be a number, not '{new_value}'"
277
+ )
278
+ minimum, maximum = schema.get("minimum"), schema.get("maximum")
279
+ if minimum is not None and number < minimum:
280
+ raise ConfigurationError(
281
+ f"The option '{name}' must be at least {minimum}"
282
+ )
283
+ if maximum is not None and number > maximum:
284
+ raise ConfigurationError(
285
+ f"The option '{name}' must be at most {maximum}"
286
+ )
287
+
288
+ old_value = row.get("Value", "").strip()
289
+ if old_value != new_value:
290
+ table.change_value(position, value_column, new_value)
291
+ changes.append(f"{name}: '{old_value}' → '{new_value}'")
292
+
293
+ return changes
@@ -0,0 +1,52 @@
1
+ """Physical constants, as plain floats in the core's canonical units.
2
+
3
+ The core does all its arithmetic on unitless numbers in one fixed set of units:
4
+
5
+ concentration M
6
+ energy kJ/mol
7
+ temperature K
8
+ rate constant 1/s
9
+ molecular mass Da
10
+
11
+ Units are attached, if at all, at the boundary: equilibrator-pathway wraps these
12
+ in pint quantities for its public API, and the browser client parses unit
13
+ strings straight into these units. Nothing in here may import pint.
14
+
15
+ These are *defined* here rather than imported from
16
+ ``equilibrator_cache.thermodynamic_constants``. Importing that module executes
17
+ ``equilibrator_cache/__init__.py``, which pulls in sqlalchemy, pandas, pooch and
18
+ rapidfuzz -- the dependency chain this package exists to avoid. The values are
19
+ held equal to equilibrator-cache's by ``tests/test_constants.py``.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ __all__ = [
25
+ "DEFAULT_RT",
26
+ "DEFAULT_T",
27
+ "PHYSIOLOGICAL_CONCENTRATION",
28
+ "STANDARD_CONCENTRATION",
29
+ "R",
30
+ ]
31
+
32
+ #: Gas constant, kJ/(mol*K).
33
+ #:
34
+ #: Note: 8.31e-3, *not* CODATA's 8.314462618e-3. This is the value
35
+ #: equilibrator-cache has always used, and every published eQuilibrator number
36
+ #: -- and every golden result this package is tested against -- was computed
37
+ #: with it. "Correcting" it would move every driving force by ~0.05%, which the
38
+ #: golden tests would (rightly) report as a regression. Change it, if ever, in
39
+ #: equilibrator-cache first and here second.
40
+ R: float = 8.31e-3
41
+
42
+ #: Default temperature, K.
43
+ DEFAULT_T: float = 298.15
44
+
45
+ #: R * DEFAULT_T, kJ/mol.
46
+ DEFAULT_RT: float = R * DEFAULT_T
47
+
48
+ #: Standard-state concentration for a solute, M.
49
+ STANDARD_CONCENTRATION: float = 1.0
50
+
51
+ #: Reference concentration for "physiological" (1 mM) Gibbs energies, M.
52
+ PHYSIOLOGICAL_CONCENTRATION: float = 1.0e-3