From 68933993ef9c416fc48eb38b5b4aee0e3d5f3631 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Fri, 6 Nov 2026 23:33:09 +0100 Subject: [PATCH 1/2] Replace figurative verbs and idioms in docstrings, comments and docs --- CHANGELOG.md | 2 +- docs/evotune_kcat_tuning.md | 16 ++++++++-------- docs/future_improvements.md | 10 +++++----- docs/gecko_light_status.md | 8 ++++---- docs/internal/brenda_refresh_plan.md | 10 +++++----- docs/internal/code_review.md | 8 ++++---- docs/internal/porting_plan.md | 2 +- docs/internal/raven_inventory.md | 6 +++--- docs/internal/ravenpy_split_report.md | 6 +++--- docs/internal/wildkcat_integration_plan.md | 10 +++++----- docs/kcat_aggregation.md | 8 ++++---- docs/migrating_from_gecko_matlab.md | 12 ++++++------ docs/raven_integration.md | 4 ++-- docs/relaxation_methods.md | 6 +++--- docs/yaml_format.md | 4 ++-- src/geckopy/adapter/adapter.py | 2 +- src/geckopy/adapter/params.py | 4 ++-- src/geckopy/data/__init__.py | 2 +- src/geckopy/databases/brenda/aggregate.py | 6 +++--- src/geckopy/databases/brenda/parse.py | 2 +- src/geckopy/databases/brenda_asset.py | 2 +- src/geckopy/databases/brenda_loader.py | 4 ++-- src/geckopy/databases/brenda_measurements.py | 2 +- src/geckopy/databases/complex_portal_loader.py | 2 +- src/geckopy/databases/dlkcat_ignore_lists.py | 6 +++--- src/geckopy/databases/phyl_dist.py | 4 ++-- src/geckopy/databases/pubchem.py | 4 ++-- src/geckopy/databases/uniprot_loader.py | 2 +- src/geckopy/ec_model/ec_model.py | 4 ++-- src/geckopy/ec_model/enzyme.py | 2 +- src/geckopy/ec_model/make_ec_model.py | 2 +- src/geckopy/ec_model/pipeline/apply_kcat.py | 4 ++-- src/geckopy/ec_model/pipeline/populate_ec.py | 4 ++-- src/geckopy/ec_model/pipeline/set_kcat.py | 2 +- .../gather_kcats/fuzzy_kcat_matching/__init__.py | 10 +++++----- .../fuzzy_kcat_matching/_brenda_query.py | 8 ++++---- src/geckopy/gather_kcats/get_standard_kcat.py | 2 +- src/geckopy/gather_kcats/kcat_assignment.py | 4 ++-- .../gather_kcats/merge_dlkcat_and_fuzzy_kcats.py | 2 +- src/geckopy/gather_kcats/merge_kcats.py | 14 +++++++------- src/geckopy/gather_kcats/select_kcat_value.py | 2 +- src/geckopy/get_enzyme_data/find_ec_in_db.py | 4 ++-- src/geckopy/kcat_tuning/corrections.py | 4 ++-- src/geckopy/kcat_tuning/evotune/distance.py | 2 +- src/geckopy/kcat_tuning/evotune/mask_size.py | 12 ++++++------ src/geckopy/kcat_tuning/evotune/parsimony.py | 14 +++++++------- src/geckopy/kcat_tuning/evotune/tuning.py | 6 +++--- src/geckopy/kcat_tuning/evotune/tying.py | 2 +- src/geckopy/kcat_tuning/sensitivity_tuning.py | 10 +++++----- src/geckopy/kcat_tuning/sigma_fitter.py | 8 ++++---- .../limit_proteins/constrain_flux_data.py | 4 ++-- .../limit_proteins/flexibilize_enz_concs.py | 16 ++++++++-------- .../limit_proteins/get_conc_control_coeffs.py | 2 +- src/geckopy/limit_proteins/relax_enz_concs.py | 2 +- .../limit_proteins/relax_proteomics_greedy.py | 2 +- src/geckopy/solver_support.py | 6 +++--- src/geckopy/upgrade.py | 2 +- src/geckopy/utilities/ec_fseof.py | 2 +- src/geckopy/utilities/ec_fva.py | 4 ++-- src/geckopy/utilities/enzyme_usage.py | 2 +- src/geckopy/utilities/get_subset_ec_model.py | 10 +++++----- src/geckopy/utilities/load_conventional_gem.py | 2 +- src/geckopy/utilities/pfba_enzymes.py | 10 +++++----- src/geckopy/utilities/save_ec_model.py | 2 +- tutorials/full_ecModel/protocol.py | 6 +++--- tutorials/light_ecModel/protocol.py | 6 +++--- 66 files changed, 177 insertions(+), 177 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 101d82d..56ea62c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * `geckopy.__version__` is the installed version of geckopy. * `cma` is a required dependency of geckopy and the extra `evotune` is removed. `import geckopy` imports `cma`. -* `enzyme_usage` reports `measured_protein`, `demanded_protein` and `demand_ratio`, the usage of the enzymes with a measured concentration divided by their measured concentrations, and the same restricted to enzymes with a usage above 1e-7 mg/gDCW (`measured_protein_active`, `demand_ratio_active`). `report_enzyme_usage` carries `demand_ratio` and `demand_ratio_active`. GECKO's `getEnzymeUsage` and `reportEnzymeUsage` give the same values. +* `enzyme_usage` reports `measured_protein`, `demanded_protein` and `demand_ratio`, the usage of the enzymes with a measured concentration divided by their measured concentrations, and the same restricted to enzymes with a usage above 1e-7 mg/gDCW (`measured_protein_active`, `demand_ratio_active`). `report_enzyme_usage` reports `demand_ratio` and `demand_ratio_active`. GECKO's `getEnzymeUsage` and `reportEnzymeUsage` give the same values. * geckopy is licensed under the GNU General Public License v3 (GPL-3.0), as RAVEN and raven-toolbox. `LICENSE`, the `license` field and the classifier of `pyproject.toml`, and the README state it. Releases up to 4.0.0b2 were published under the MIT licence. * `load_prot_data` takes `zeros_as_measured`: by default an abundance of 0 is read as not measured (NaN) before any filter, also with `filter_data=False`, so the mean of a protein with a zero replicate leaves the zeros out; with `True` a 0 counts as a measured replicate, is kept in the mean and is exempt from `min_val` and `cut_lowest`, as GECKO's `loadProtData` option `zerosAsMeasured`. * `get_isozyme_coverage` reports the isozyme groups of `model.ec.rxns` that have both a measured and an unmeasured entry, and the enzymes that `zero_unmeasured_isozymes` can limit, as GECKO's `getIsozymeCoverage`. diff --git a/docs/evotune_kcat_tuning.md b/docs/evotune_kcat_tuning.md index 0292ef8..79a66cf 100644 --- a/docs/evotune_kcat_tuning.md +++ b/docs/evotune_kcat_tuning.md @@ -71,7 +71,7 @@ fructose NaN 0.338 NaN -1000 ... 1 `Ptot` is measured protein content (`NaN` if not measured), each ` ()` column is a measured or fixed flux for that exchange reaction, `evotuneRMSEweight` scales how much that row counts toward -the RMSE (usually `1`), and `source` is free text carried through for +the RMSE (usually `1`), and `source` is free text kept unchanged for your own bookkeeping. `evotuneZeroExch.tsv` is simpler -- a `Rxns` header, then one reaction ID per line. @@ -110,7 +110,7 @@ What each knob means: before it moves that kcat far. `custom` (curated by hand) at 0.1 is three times more trusted than an unlabelled prediction at 0.3. This is the one section with no universal default -- your model's own - source labels decide it. + source labels determine it. - **`max_growth_weight`** (default `1.0`). The combined score is `(rmse_flux + w * rmse_max_growth) / (w + 1)`. At `2`, growth-rate error counts double against flux error -- useful when growth rate is @@ -130,7 +130,7 @@ What each knob means: `sigma0_log` and of the objective itself -- `tune_prior_penalty_weight` runs a candidate sweep and reports fit cost against cross-seed reproducibility so it doesn't have to be - picked blind (see its docstring; it costs one full tuning run per + chosen without information (see its docstring; it costs one full tuning run per candidate per seed, so treat it as an occasional calibration step, not part of routine tuning). - **`tie_isozymes`** (default `true`). Several ecModel reactions are @@ -163,10 +163,10 @@ no tuning happens yet. The result is a table, one row per kcat or tied group, ranked by that leverage weighted by trust: the reaction ID, how many isozyme copies it covers, its source group, its current value, its leverage, and `cum_leverage_share` -- the running fraction of -total leverage carried by this row and every row above it. The top +total leverage accounted for by this row and every row above it. The top rows are what a curator should look at first, independent of whether you ever run a tuning search: on ecYeastGEM, a handful of kcats -routinely carry the majority of what any tuning run could achieve, and +routinely account for the majority of what any tuning run could achieve, and they tend to be implausible database values rather than genuine biology. @@ -222,7 +222,7 @@ come back in input order, and looking odd is all you get. `coverage` (a share of flagged leverage) or `top` (a row count) then truncates the report: on ecYeastGEM, tightening the checks themselves does not work half as well as this does, since `coverage=0.8` cuts 2575 unfiltered -rows to 33, and the first row alone carries half. +rows to 33, and the first row alone accounts for half. ## Step 5: Tune @@ -247,7 +247,7 @@ or an absolute leverage value, so the same setting means a comparable thing on a different model. Pass `tunable_mask` directly instead if you want to fix the set yourself. -`model` is mutated in place: on return, every tunable kcat carries the +`model` is mutated in place: on return, every tunable kcat has the best value CMA-ES found, and the model's kcat constraints are already applied -- nothing further is needed before simulating or saving it. Beyond `target_impact_share`, the only other knobs are `popsize` @@ -286,7 +286,7 @@ Before trusting a tuned kcat, check more than the final RMSE: tools for this: `n_changed`, `fold_change`, `source_movement`. 2. **Impact share** -- `parsimony.impact_share` reports what fraction of the total achievable improvement the changed kcats actually - carry, distinguishing "few, large, and consequential" from mere + account for, distinguishing "few, large, and consequential" from mere sparsity. This is a different, unweighted quantity from `screen_kcat_leverage`'s trust-weighted `cum_leverage_share`. 3. **Reproducibility.** Rerun with a different `seed`. Large diff --git a/docs/future_improvements.md b/docs/future_improvements.md index 6bb19a2..716c7dd 100644 --- a/docs/future_improvements.md +++ b/docs/future_improvements.md @@ -39,20 +39,20 @@ fast, and so we have a list of things to retest when we upgrade. kernels.** Even a trivial 2-worker pool with a `lambda x: x*2` task hangs forever — no error, no timeout. The original plan for parallel `ec_fva` called for `spawn` because it's the only context - available on Windows. Workaround: pick the context based on the + available on Windows. Workaround: select the context based on the platform — `fork` on POSIX (which is also faster, no module re-import), `spawn` on Windows. See `geckopy.utilities.ec_fva.ec_fva`. - **`ruamel.yaml`'s `ScalarInt` / `ScalarFloat` leak out of cobra YAML loads.** After loading a YAML model with cobra, attributes like `Metabolite.charge` are ruamel scalar types (subclasses of - int/float that carry YAML formatting metadata). The ruamel + int/float that have YAML formatting metadata). The ruamel safe-dumper has no representer for them, so writing the model back out fails with `RepresenterError: cannot represent an object: 0`. Workaround: walk the assembled document and coerce everything to plain Python primitives just before writing. See - `geckopy.utilities.save_ec_model._to_native`. This bites any - YAML-to-YAML round-trip of a cobra-loaded model. + `geckopy.utilities.save_ec_model._to_native`. This affects any + write of a cobra-loaded model back to YAML. ## Deferred migrations @@ -131,7 +131,7 @@ Notable items: org-SA (output rank 5) BEFORE any-organism-no-substrate-kcat (output rank 4). When both are available for a reaction, MATLAB returns the org-SA result and reports its origin as 5, even though - the docstring's origin ranking implies origin 4 should win. Either + the docstring's origin ranking implies origin 4 should be kept. Either swap the search order so any-no-subs-kcat is tried before org-SA, or update the docstring to match the search order. geckopy replicates the current MATLAB behavior, both within one EC number diff --git a/docs/gecko_light_status.md b/docs/gecko_light_status.md index 1a7a2e3..a4db8b6 100644 --- a/docs/gecko_light_status.md +++ b/docs/gecko_light_status.md @@ -22,7 +22,7 @@ encode the protein cost of that catalysis. There are two ways: single shared protein pool. Each catalysed cobra reaction gets one extra stoichiometric coefficient `MW_sum / (kcat * 3600)` against that pool. Isozymes are not split into separate cobra reactions; - instead `apply_kcat_constraints` picks the lowest-cost isozyme + instead `apply_kcat_constraints` selects the lowest-cost isozyme (smallest `MW_sum / kcat`) for each reaction and writes that one coefficient. The LP stays roughly the same size as the starting GEM. The trade-off is that you can no longer answer per-enzyme questions @@ -55,7 +55,7 @@ isn't checked out next to geckopy. | | Full | Light | |---|---|---| | Isozyme expansion | reactions split, one copy per isozyme with a `_EXP_` suffix | cobra reactions stay singular | -| `ec.rxns` | one entry per (reaction, isozyme) pair; ids match the cobra reactions exactly | duplicate entries per isozyme; ids carry a `###_` counter prefix (e.g. `001_R2`, `002_R2`) | +| `ec.rxns` | one entry per (reaction, isozyme) pair; ids match the cobra reactions exactly | duplicate entries per isozyme; ids have a `###_` counter prefix (e.g. `001_R2`, `002_R2`) | | Per-enzyme pseudo-metabolite `prot_` | yes | no | | Per-enzyme usage reaction `usage_prot_` | yes | no | | Shared protein pool | yes | yes (single global cost applied directly to each catalysed reaction's stoichiometry) | @@ -66,11 +66,11 @@ isn't checked out next to geckopy. |---|---| | `src/geckopy/ec_model/make_ec_model.py` | dispatches stage 5 (isozyme split) and stages 9/11 (per-enzyme prot mets + usage rxns) off; calls `allocate_ec_and_coupling_light` instead of the full stages 6/8 | | `src/geckopy/ec_model/pipeline/populate_ec.py` | `allocate_ec_and_coupling_light` builds `ec.rxns` (one row per isozyme with the `###_` prefix) and `ec.rxn_enz_mat` in one pass; `split_light_rxn_id` is the inverse | -| `src/geckopy/ec_model/pipeline/apply_kcat.py` | `_apply_kcat_constraints_light` groups ec rows by cobra reaction, picks the lowest-cost isozyme, writes `-MW_sum / (kcat * 3600)` as the `prot_pool` coefficient; same clear-then-write idempotency contract as full | +| `src/geckopy/ec_model/pipeline/apply_kcat.py` | `_apply_kcat_constraints_light` groups ec rows by cobra reaction, selects the lowest-cost isozyme, writes `-MW_sum / (kcat * 3600)` as the `prot_pool` coefficient; same clear-then-write idempotency contract as full | | `src/geckopy/ec_model/pipeline/set_kcat.py` | the `###_` prefix is part of the id in a light model: `001_R2` matches that one row and a bare cobra id such as `R2` matches nothing | | `src/geckopy/ec_model/enzyme.py` | `Enzyme.reactions` strips the prefix and dedupes; `Enzyme.mw` setter passes ec.rxns ids (not cobra ids) to `apply_kcat_constraints`; `_repr_html_` catches the light NotImplementedError | | `src/geckopy/gather_kcats/fuzzy_kcat_matching.py` | strips the `###_` prefix for cobra lookups | -| `src/geckopy/gather_kcats/get_standard_kcat.py` | `_ec_rxn_to_cobra_id` handles both forms | +| `src/geckopy/gather_kcats/get_standard_kcat.py` | `_ec_rxn_to_cobra_id` accepts both forms | Tests covering the light path: diff --git a/docs/internal/brenda_refresh_plan.md b/docs/internal/brenda_refresh_plan.md index e6d2d4e..90668b8 100644 --- a/docs/internal/brenda_refresh_plan.md +++ b/docs/internal/brenda_refresh_plan.md @@ -1,7 +1,7 @@ # BRENDA database refresh plan > **Historical planning doc.** The implementation diverged in May 2026: -> the snapshot now ships **both** max and median per (ec, substrate, +> the snapshot now includes **both** max and median per (ec, substrate, > organism) triple in long-format `kcat.tsv` / `sa.tsv` (not just max > in `max_kcat.tsv` etc.). See [kcat_aggregation.md](kcat_aggregation.md) > for the current schema and rationale. @@ -285,7 +285,7 @@ tests/ - One-off: `geckopy brenda-refresh` -> three new TSVs in `src/geckopy/data/brenda/`. -- Spot-check against the 2018 files: pick 5 well-studied ECs +- Spot-check against the 2018 files: select 5 well-studied ECs (`1.1.1.1`, `2.7.1.40`, `3.4.21.1`, `4.2.1.1`, `1.11.1.6`), diff max-kcat per organism, sanity-check the order of magnitude. - Copy the three files into [GECKO/databases/](../../GECKO/databases/) @@ -339,15 +339,15 @@ tests/ ## Attribution -The TSV header carries `CC BY 4.0`; add a one-paragraph +The TSV header contains `CC BY 4.0`; add a one-paragraph attribution to [README.md](../README.md) crediting BRENDA (citation: Chang et al., Nucleic Acids Res., latest annual update) and linking to . ## Rollout -1. Land this PR. Refresh once on commit, ship the new TSVs in the - geckopy repo, ship the one-line `loadBRENDAdata.m` patch upstream +1. Land this PR. Refresh once on commit, include the new TSVs in the + geckopy repo, submit the one-line `loadBRENDAdata.m` patch upstream to GECKO. 2. Future refreshes: re-run `geckopy brenda-refresh` when BRENDA publishes a new release (announcement on diff --git a/docs/internal/code_review.md b/docs/internal/code_review.md index 32ed1c1..96ea80c 100644 --- a/docs/internal/code_review.md +++ b/docs/internal/code_review.md @@ -4,19 +4,19 @@ The kcat-aggregation thread is fully resolved. ## kcat aggregation — resolved -Decision: keep all shipped defaults at the MATLAB-GECKO values. The +Decision: keep all included defaults at the MATLAB-GECKO values. The adapter exposes three dedicated fields for project-wide overrides, and -the BRENDA snapshot ships both max and median per (ec, substrate, +the BRENDA snapshot includes both max and median per (ec, substrate, organism) triple so flipping the runtime default does not require regenerating the snapshot. -| function | adapter param | shipped default | other allowed values | +| function | adapter param | included default | other allowed values | |---|---|---|---| | `fuzzy_kcat_matching` | `params.kcat_aggregate_brenda` | `"max"` | `"median"` | | `apply_kcat_list` | `params.kcat_aggregate_candidates` | `"max"` | `"min"`, `"median"`, `"mean"` | | `fill_kcats_from_isozymes` | `params.kcat_aggregate_isozymes` | `"mean"` | `"max"`, `"median"` | -`fuzzy_kcat_matching` now picks the matching snapshot view +`fuzzy_kcat_matching` now selects the matching snapshot view (`brenda.kcat_for(aggregate)` / `brenda.sa_for(aggregate)`) and applies the same aggregation across the rows that survive the EC + organism + substrate gates. So both layers — the per-triple snapshot collapse and diff --git a/docs/internal/porting_plan.md b/docs/internal/porting_plan.md index a2587eb..9c2c66d 100644 --- a/docs/internal/porting_plan.md +++ b/docs/internal/porting_plan.md @@ -26,7 +26,7 @@ optimum; floating-point math differs between Python and MATLAB). ## Data model -`EcModel` subclasses `cobra.Model` and carries an extra `.ec` +`EcModel` subclasses `cobra.Model` and has an extra `.ec` attribute. `.ec` is an `EcData` dataclass that holds the kcat array, enzyme list, MW array, the sparse reaction-enzyme coupling matrix, and a few other vectors. See diff --git a/docs/internal/raven_inventory.md b/docs/internal/raven_inventory.md index 332df16..cf3e4e8 100644 --- a/docs/internal/raven_inventory.md +++ b/docs/internal/raven_inventory.md @@ -2,7 +2,7 @@ GECKO MATLAB is built on top of [RAVEN](https://github.com/CompMetEng/RAVEN), a MATLAB toolkit -for genome-scale metabolic modelling. RAVEN handles all the +for genome-scale metabolic modelling. RAVEN does all the low-level work: parsing SBML files, editing reactions and metabolites, solving FBAs, manipulating gene-protein-reaction rules, and so on. @@ -109,7 +109,7 @@ their job is already handled inside cobra-py / optlang. | Function | Why it's not ported | |---|---| -| `solveLP(..., minFlux, hsSol)` hot-start | MATLAB-specific solver state; optlang / cobra-py handle warm-start internally | +| `solveLP(..., minFlux, hsSol)` hot-start | MATLAB-specific solver state; optlang / cobra-py do warm-start internally | | `setRavenSolver(...)` | MATLAB CPLEX / Gurobi setup; cobra-py uses `model.solver = "gurobi"` | | `importExcelModel`, `checkFileExistence`, dialog UI prompts | File I/O glue, not algorithmic | | `convertCharArray`, `dispEM` | MATLAB cell-array and struct utilities | @@ -127,7 +127,7 @@ The compartment-manipulation functions (`mergeCompartments`, `copyToCompartments`) and `removeBadRxns` are next on the list but get called in fewer places. -The YAML reader/writer is its own concern. geckopy already picked +The YAML reader/writer is its own concern. geckopy already uses a canonical cobra-py-compatible schema (see [yaml_format.md](yaml_format.md)), so the RAVEN-dialect reader is really only useful for backward compatibility with legacy files. diff --git a/docs/internal/ravenpy_split_report.md b/docs/internal/ravenpy_split_report.md index 15f9ca0..c29de86 100644 --- a/docs/internal/ravenpy_split_report.md +++ b/docs/internal/ravenpy_split_report.md @@ -15,7 +15,7 @@ geckopy — everything specific to enzyme-constrained GEMs (depends on ravenpy The test applied to every function below: **"Would a researcher working on a plain GEM (no enzyme constraints) ever want this?"** If yes → `ravenpy`. If it only makes -sense once a model carries kcats / a protein pool / enzyme usage → `geckopy`. +sense once a model contains kcats / a protein pool / enzyme usage → `geckopy`. A second, sharper test for the borderline cases: **does the function actually read or write the `ec` substructure, or does it just operate on a `cobra.Model`?** Many @@ -33,7 +33,7 @@ wrapper left in geckopy." Those are called out explicitly. ## Tier A — move as-is (clearly generic, little/no coupling) These operate on a `cobra.Model` (or raw data) and have obvious value for non-ec GEMs. -Several already carry docstrings naming their RAVEN MATLAB origin. +Several already have docstrings naming their RAVEN MATLAB origin. ### Structural model surgery — `ec_model/pipeline/preprocess.py`, `expand.py` | function | what it does | why generic | @@ -208,7 +208,7 @@ For completeness, these are correctly ec-specific and should **not** move: ## Suggested migration order 1. **Tier A structural surgery** (`preprocess.py`, `expand.py`) — zero `.ec` coupling, - already RAVEN-annotated, immediate win. + already RAVEN-annotated, immediately usable. 2. **Tier A databases + `flux_data`/proteomics + `apply_flux_data_constraints`** — but first do the **adapter split** (Tier C) so they don't drag the ec adapter along. 3. **Tier B EC-code assignment and FSEOF** — refactor to operate on `cobra.Model`, diff --git a/docs/internal/wildkcat_integration_plan.md b/docs/internal/wildkcat_integration_plan.md index bb5e683..7c6f13a 100644 --- a/docs/internal/wildkcat_integration_plan.md +++ b/docs/internal/wildkcat_integration_plan.md @@ -66,7 +66,7 @@ tests/ [project.optional-dependencies] wildkcat = ["wildkcat>=", "python-dotenv>=1"] ``` - Pick `` from the first PyPI release that ships the + Select `` from the first PyPI release that includes the programmatic entry point we depend on (see step 1 below). - [src/geckopy/gather_kcats/__init__.py](../../src/geckopy/gather_kcats/__init__.py) — re-export `gather_kcats_wildkcat` **inside a lazy import** @@ -100,7 +100,7 @@ tests/ `(rxn_id, ec_number, substrate_name | substrate_inchi | substrate_smiles, organism)`. - Record the **output schema** WILDkCAT emits and which columns - carry kcat (s^-1 vs min^-1 — confirm and unit-convert in + contain kcat (s^-1 vs min^-1; confirm and unit-convert in the adapter if needed). ### 2. `config.py` @@ -123,7 +123,7 @@ tests/ same first positional arg, same return type, no surprises. - Build the WILDkCAT input table by walking `model.ec.rxns`: - `rxn_id` <- `model.ec.rxns[i]` (preserve the `ec_` prefix - for light models so the adapter can round-trip) + for light models so the adapter can read the id back) - `ec_number` <- `model.ec.eccodes[i]` - `substrate` <- substrate name(s) from the EC reaction's metabolites; reuse the substrate-extraction logic that @@ -192,7 +192,7 @@ tab-completion until the optional dep is present. [README.md](../../README.md) how to enable it. - Target test count after this PR: 172 (current) + ~12 new. -### 7. Benchmark helper (optional, can ship in the same PR) +### 7. Benchmark helper (optional, can be included in the same PR) `src/geckopy/gather_kcats/benchmark.py`: @@ -228,7 +228,7 @@ decision has nothing to stand on. - Removing or deprecating [fuzzy_kcat_matching](../../src/geckopy/gather_kcats/fuzzy_kcat_matching/) or DLKcat wrappers. Tracked for a follow-up after benchmarks. - Wrapping CataPro standalone (Strategy D). The full WILDkCAT - pipeline already drives CataPro. + pipeline already runs CataPro. - Promoting `gather_kcats_wildkcat` to top-level `geckopy.*`. - Changing the MATLAB GECKO side. diff --git a/docs/kcat_aggregation.md b/docs/kcat_aggregation.md index ef3879c..a28eff1 100644 --- a/docs/kcat_aggregation.md +++ b/docs/kcat_aggregation.md @@ -1,6 +1,6 @@ # kcat aggregation: how far is `max` from `median` on BRENDA? -The BRENDA files (toolbox-assets release `brenda-2026.1-1`) carry **both** the per-(ec, substrate, +The BRENDA files (toolbox-assets release `brenda-2026.1-1`) contain **both** the per-(ec, substrate, organism) max and the per-triple median of all raw measurements that fell into the triple. They live in the same `kcat.tsv` / `sa.tsv` file distinguished by an `aggregation` column; `load_brenda_data` splits @@ -40,7 +40,7 @@ Across those: |---|---:|---:|---:| | within-triple `log10(max / median)` | 0.05 / 0.16 / 0.28 | **0.26** | 0.58 | -So within a triple the max sits typically ~1.8× above the median; +So within a triple the max is typically ~1.8× above the median; 4.8 % of multi-measurement triples have max ≥ 10× the median, 1.2 % have max ≥ 100×. Small effect — *within* a triple BRENDA measurements are usually close enough that picking max vs median doesn't move the @@ -76,7 +76,7 @@ median-of-medians: | `maxmax ≥ 100× medmed` | 24.2 % | | `maxmax ≥ 1 000× medmed` | 8.6 % | -For 60 % of well-measured ECs, the shipped default kcat is ≥ 10× the +For 60 % of well-measured ECs, the included default kcat is ≥ 10× the typical BRENDA assay; for 8.6 % it is ≥ 1 000× above. Most of that comes from the runtime aggregation layer (`maxmax / maxmed` ≈ 1.38), with a small further bias from the snapshot layer (`maxmed / medmed` @@ -143,7 +143,7 @@ ec_code substrate organism kcat_max kcat_median n references ``` One **wide** row per (ec, substrate, organism) triple. `kcat_max` and -`kcat_median` carry the two statistics computed from the raw +`kcat_median` contain the two statistics computed from the raw measurements that fell into the triple. `n` is the count of raw measurements aggregated (so `n=1` rows have max == median, as above). `sa.tsv` has the same shape with `sa_max` / `sa_median` columns. diff --git a/docs/migrating_from_gecko_matlab.md b/docs/migrating_from_gecko_matlab.md index 10ae2f1..dd4a3b4 100644 --- a/docs/migrating_from_gecko_matlab.md +++ b/docs/migrating_from_gecko_matlab.md @@ -16,7 +16,7 @@ them require you to change your model files. > geckopy also follows — see > [GECKO 3 to GECKO 4](https://gecko-docs.readthedocs.io/en/latest/gecko3-to-gecko4.html) > for the full list. Throughout this guide, individual functions that -> are affected by those 3→4 changes carry a **`[3→4]`** marker; that +> are affected by those 3→4 changes have a **`[3→4]`** marker; that > marker is geckopy's signal that the function's behaviour or schema > differs from what GECKO 3 did, not a Python-vs-MATLAB difference. @@ -194,7 +194,7 @@ like `data_col` (default `0`) and `condition` (in `apply_flux_data_constraints`) Implemented end-to-end. `make_ec_model(model, adapter, gecko_light=True)` produces a working light ecModel; `apply_kcat_constraints`, `set_kcat_for_reactions`, and the `Enzyme` proxy's read paths all -handle the light layout. The per-enzyme prot-pool analyses (proteomics +work on the light layout. The per-enzyme prot-pool analyses (proteomics integration via `constrain_enz_concs`, `flexibilize_enz_concs`, `pfba_enzymes`) and the isozyme-aggregating helpers (`fill_kcats_from_isozymes`) raise `NotImplementedError` on a light @@ -420,11 +420,11 @@ empty-ecModel guard, provenance injection). See [raven_integration.md](raven_integration.md) for the split, and [yaml_format.md](yaml_format.md) for the schema reference. -**BRENDA data files.** **`[3→4]`** GECKO 4 / geckopy ship +**BRENDA data files.** **`[3→4]`** GECKO 4 / geckopy include `kcat.tsv` / `sa.tsv` / `mw.tsv` (refreshed from the BRENDA bulk JSON; bare EC numbers, plain organism names, `#` release header, `references` column). GECKO 3's `max_KCAT.txt` etc. are no longer -read. **`[Py]`** geckopy additionally ships both **max** and +read. **`[Py]`** geckopy additionally includes both **max** and **median** rows per (ec, substrate, organism) triple in one file, distinguished by an `aggregation` column. The default `kcat_aggregate_brenda = "max"` preserves MATLAB-GECKO behaviour. @@ -438,7 +438,7 @@ See [kcat_aggregation.md](kcat_aggregation.md). The port found and fixed several GECKO 3 bugs (EC-code assignment, sigma fitting, standard-kcat subsystem means, duplicate EC codes, -…); **`[3→4]`** GECKO 4 carries the same fixes, so the two toolboxes +…); **`[3→4]`** GECKO 4 includes the same fixes, so the two toolboxes agree. The full per-function divergence list lives on the [GECKO vs. geckopy](https://gecko-docs.readthedocs.io/en/latest/gecko-to-geckopy.html) page of gecko-docs.readthedocs.io, plus [future_improvements.md](future_improvements.md) @@ -453,7 +453,7 @@ for the MATLAB-side items the port surfaced. - **Forward-direction protein reactions** (`usage_prot_*` / `prot_pool_exchange`). - **Refreshed BRENDA TSV schema** (geckopy adds the max+median - variant; MATLAB ships only max so far). + variant; MATLAB includes only max so far). ### geckopy-only (no MATLAB counterpart) diff --git a/docs/raven_integration.md b/docs/raven_integration.md index 30f83db..f04d56d 100644 --- a/docs/raven_integration.md +++ b/docs/raven_integration.md @@ -27,11 +27,11 @@ operate on the populated struct. raven-toolbox's `read_yaml_model` and `write_yaml_model`: -- handle the cobra-shaped portion (metabolites / reactions / genes / compartments / annotations); +- read and write the cobra-shaped portion (metabolites / reactions / genes / compartments / annotations); - on read, parse the `ec-rxns` / `ec-enzymes` / `gecko_light` top-level sections into a typed `EcData` (`raven_toolbox.io.ec_data`) and attach it as `model.ec`; - on write, serialise `model.ec` back to the same top-level sections when present; - normalise three legacy MATLAB GECKO quirks transparently: top-level `smiles` → `annotation['smiles']`, reverse-direction `usage_prot_*` / `prot_pool_exchange` flipped to forward, bare-`-` document root merged into one mapping; -- preserve other unknown top-level keys opaquely via `model.notes['_yaml_sections']` for round-trip. +- preserve other unknown top-level keys opaquely via `model.notes['_yaml_sections']` so that they are written back unchanged. geckopy's `save_ec_model` / `load_ec_model` are thin wrappers that add three application-level concerns on top: diff --git a/docs/relaxation_methods.md b/docs/relaxation_methods.md index 716e4e3..196333a 100644 --- a/docs/relaxation_methods.md +++ b/docs/relaxation_methods.md @@ -16,8 +16,8 @@ the per-complex options for all of them are described at the end. | Origin | GECKO MATLAB `flexibilizeEnzConcs.m` (faithful port) | legacy geckopy, Carrasco et al. 2023 | | Which enzyme to relax | largest **control coefficient** (how much each measured enzyme's bound limits growth, from `get_conc_control_coeffs`: the reduced cost of its usage bound, or a finite difference where the solver exposes no duals) | largest **\|shadow price\|** (LP dual of the usage upper bound) | | Solves per selection | one LP solve plus one check solve per enzyme with a nonzero coefficient (one solve per candidate protein where the solver exposes no duals) | a **single** `slim_optimize` exposes all duals (cheap) | -| Relaxation step | **gradual**: usage `ub ← conc·(1 + fold_change·k)`; the same enzyme can be picked repeatedly and loosened a bit more each time | **all-or-nothing**: usage `ub ← default_upper_bound` (≈ unconstrained) in one shot | -| Tighten-back | yes — a refinement pass minimises the protein pool at `exp_growth` and **drops** enzymes whose true usage is below their original conc (un-relaxes over-relaxed ones) | none — each picked enzyme stays fully unconstrained | +| Relaxation step | **gradual**: usage `ub ← conc·(1 + fold_change·k)`; the same enzyme can be selected repeatedly and loosened a bit more each time | **all-or-nothing**: usage `ub ← default_upper_bound` (≈ unconstrained) in one shot | +| Tighten-back | yes: a refinement pass minimises the protein pool at `exp_growth` and **drops** enzymes whose true usage is below their original conc (un-relaxes over-relaxed ones) | none: each selected enzyme stays fully unconstrained | | Result stringency | **tight** (minimal, proteomics-faithful relaxation) | **loose** (tends to over-relax) | | Pool fallback | yes — relaxes `prot_pool_exchange` when no single enzyme helps | no | | Scope | measured enzymes only (`~isnan(concs)`) | any currently-constrained enzyme (`ub < default`) | @@ -51,7 +51,7 @@ follow-ups would make them more interchangeable: output is not systematically looser than `flexibilize_enz_concs` (this is the over-relaxation point in the modeling-intent review, §3.3). 2. Align the failure convention — currently one returns a `converged=False` - result and the other raises; pick one so callers don't special-case each. + result and the other raises; select one so callers don't special-case each. ## `relax_enz_concs`: the least total relaxation, in one LP diff --git a/docs/yaml_format.md b/docs/yaml_format.md index 516ada8..8ee95ad 100644 --- a/docs/yaml_format.md +++ b/docs/yaml_format.md @@ -25,7 +25,7 @@ calls cobrapy for the cobra-shaped portion and parses the GECKO ## Design goals 1. **Identical to cobrapy.** The cobra-shaped portion is exactly what - `cobra.io.model_to_dict` / `model_from_dict` round-trips; no custom + `cobra.io.model_to_dict` / `model_from_dict` write and read back; no custom parser, no custom schema. 2. **Round-trippable with MATLAB GECKO.** RAVEN's `writeYAMLmodel` and `readYAMLmodel` emit and accept this same format. @@ -117,7 +117,7 @@ entry here. Fields: | Field | Type | Required? | Notes | |---|---|---|---| -| `id` | string | yes | Matches a model reaction id; may carry `_EXP_` and/or `_REV` suffixes | +| `id` | string | yes | Matches a model reaction id; may have `_EXP_` and/or `_REV` suffixes | | `kcat` | number | yes | Turnover number in s^-1; write NaN as `.nan` | | `source` | string | optional | Where the kcat came from (`"brenda"`, `"dlkcat"`, `"manual"`, ...). Default `""` | | `notes` | string | optional | Free-form note. Default `""` | diff --git a/src/geckopy/adapter/adapter.py b/src/geckopy/adapter/adapter.py index c3317bb..25c3d15 100644 --- a/src/geckopy/adapter/adapter.py +++ b/src/geckopy/adapter/adapter.py @@ -50,7 +50,7 @@ class ModelAdapter: Subclass only when a particular organism needs custom logic (e.g. an unusual way to identify spontaneous reactions, or a bespoke gene-id mapping that the default UniProt-table-driven - one can't handle). + one does not support). """ def __init__(self, params: ModelParameters): diff --git a/src/geckopy/adapter/params.py b/src/geckopy/adapter/params.py index da65ecb..8778c70 100644 --- a/src/geckopy/adapter/params.py +++ b/src/geckopy/adapter/params.py @@ -101,8 +101,8 @@ class MatlabParams(BaseModel): class KcatStageParams(BaseModel): - """One ``[[kcat_assignment.stage]]``: decides each reaction that is still - open, by the first listed source with a value (``select``) or by all + """One ``[[kcat_assignment.stage]]``: assigns a kcat to each reaction that is still + open, from the first listed source with a value (``select``) or from all listed sources with a value, weighted by their reliability (``combine``).""" model_config = ConfigDict(extra="forbid") diff --git a/src/geckopy/data/__init__.py b/src/geckopy/data/__init__.py index da95d35..19b04cd 100644 --- a/src/geckopy/data/__init__.py +++ b/src/geckopy/data/__init__.py @@ -1,4 +1,4 @@ -"""Default data files shipped with geckopy. +"""Default data files included with geckopy. Accessed via ``importlib.resources.files("geckopy.data")``. """ diff --git a/src/geckopy/databases/brenda/aggregate.py b/src/geckopy/databases/brenda/aggregate.py index 86489c0..2b60cd8 100644 --- a/src/geckopy/databases/brenda/aggregate.py +++ b/src/geckopy/databases/brenda/aggregate.py @@ -23,7 +23,7 @@ # Output filenames per kind. Plain ``.tsv``; the historical -# ``max_.tsv`` name would be wrong now that the same row carries +# ``max_.tsv`` name would be wrong now that the same row has # both max and median. _OUTPUT_FILES: dict[Kind, tuple[str, str]] = { "kcat": ("kcat.tsv", "kcat in 1/s"), @@ -55,7 +55,7 @@ "mw": _MW_HEADERS, } -# kcat and SA carry both max and median per triple; MW is single-valued. +# kcat and SA have both max and median per triple; MW is single-valued. _DUAL_AGG_KINDS: tuple[Kind, ...] = ("kcat", "sa") @@ -144,5 +144,5 @@ def aggregate_and_write( def _fmt_value(v: float) -> str: # Python ``str(float)`` always includes a decimal point or exponent, - # so ``598.0`` and ``2.5`` round-trip identically. + # so ``598.0`` and ``2.5`` are written and read back identically. return str(v) diff --git a/src/geckopy/databases/brenda/parse.py b/src/geckopy/databases/brenda/parse.py index a5eb7e6..7674350 100644 --- a/src/geckopy/databases/brenda/parse.py +++ b/src/geckopy/databases/brenda/parse.py @@ -160,7 +160,7 @@ def _parse_one( for pid in raw.get("proteins", []) or []: # The "protein" map is keyed by string (JSON object keys always are), - # while the "proteins" reference list may carry ints; coerce so the + # while the "proteins" reference list may contain ints; coerce so the # lookup matches regardless and the measurement is not silently lost. protein = proteins_map.get(str(pid)) if not isinstance(protein, dict): diff --git a/src/geckopy/databases/brenda_asset.py b/src/geckopy/databases/brenda_asset.py index 8119f83..fce209b 100644 --- a/src/geckopy/databases/brenda_asset.py +++ b/src/geckopy/databases/brenda_asset.py @@ -1,6 +1,6 @@ """The BRENDA files of geckopy, from a CompMetEng/toolbox-assets release. -geckopy does not ship BRENDA data. The files of release ``brenda-2026.1-1`` +geckopy does not include BRENDA data. The files of release ``brenda-2026.1-1`` (BRENDA 2026.1, CC BY 4.0) are downloaded on first use, checked against their SHA-256 checksums, decompressed and cached by raven-toolbox (``raven_toolbox.data.ensure_data_file``). GECKO reads the same release diff --git a/src/geckopy/databases/brenda_loader.py b/src/geckopy/databases/brenda_loader.py index 4b10284..1e49a77 100644 --- a/src/geckopy/databases/brenda_loader.py +++ b/src/geckopy/databases/brenda_loader.py @@ -19,7 +19,7 @@ (``*`` for SA), organism, value-max, value-median, n (number of raw measurements aggregated), references (semicolon-joined PMIDs or ``*``). MW has six columns (single value, no aggregation choice). A -``#`` header line carries the BRENDA release version; the line after +``#`` header line contains the BRENDA release version; the line after it is the TSV column header (also skipped). """ from __future__ import annotations @@ -345,7 +345,7 @@ def _sa_view_from_wide(wide: pd.DataFrame, value_col: str) -> pd.DataFrame: def _join_sa_with_mw(sa: pd.DataFrame, mw: pd.DataFrame) -> pd.DataFrame: - """Join SA + MW on (EC, organism) case-insensitive, first MW match wins. + """Join SA + MW on (EC, organism) case-insensitive, the first MW match is kept. For each SA row, find the first MW row with the same EC and organism (case-insensitive). If found, emit a row with the diff --git a/src/geckopy/databases/brenda_measurements.py b/src/geckopy/databases/brenda_measurements.py index abc5712..224f2a3 100644 --- a/src/geckopy/databases/brenda_measurements.py +++ b/src/geckopy/databases/brenda_measurements.py @@ -1,6 +1,6 @@ """Load the BRENDA measurement table: one row per wild-type kcat measurement. -``measurements.tsv`` sits next to ``kcat.tsv``, ``sa.tsv`` and ``mw.tsv`` in +``measurements.tsv`` is next to ``kcat.tsv``, ``sa.tsv`` and ``mw.tsv`` in the BRENDA folder (see ``ModelAdapter.get_brenda_db_folder``). It holds the wild-type BRENDA kcats with an EC number after the outlier screen, with the substrate, organism, UniProt accession and sequence id of each measurement. diff --git a/src/geckopy/databases/complex_portal_loader.py b/src/geckopy/databases/complex_portal_loader.py index a20f8c8..6135fc3 100644 --- a/src/geckopy/databases/complex_portal_loader.py +++ b/src/geckopy/databases/complex_portal_loader.py @@ -99,7 +99,7 @@ def _as_list(value): """Wrap a scalar in a single-element list; pass lists through. ComplexPortal exports collapse single-element arrays to scalars - (matching MATLAB's `jsonencode` round-trip behaviour); this + (matching the behaviour of MATLAB's `jsonencode` when the file is written and read back); this normalises them back to lists. """ if isinstance(value, list): diff --git a/src/geckopy/databases/dlkcat_ignore_lists.py b/src/geckopy/databases/dlkcat_ignore_lists.py index 60e52b9..b4d4bef 100644 --- a/src/geckopy/databases/dlkcat_ignore_lists.py +++ b/src/geckopy/databases/dlkcat_ignore_lists.py @@ -61,7 +61,7 @@ def load_dlkcat_ignore_lists( Looks for ``DLKcatIgnoreMets.tsv`` and ``DLKcatCurrencyMets.tsv`` in ``project_data_folder`` first; falls back to the defaults - shipped inside ``geckopy.data`` for any file missing from the + included in ``geckopy.data`` for any file missing from the project folder. The two files are independent: the project folder may override @@ -76,7 +76,7 @@ def load_dlkcat_ignore_lists( project_data_folder Project's ``data/`` folder, typically ``adapter.params.path / "data"``. If ``None`` (or the file - is absent there), the geckopy-shipped default is used. + is absent there), the default included with geckopy is used. Returns ------- @@ -120,7 +120,7 @@ def load_dlkcat_ignore_lists( def _resolve_path(project_data_folder, filename: str): - """Project file if present; else the shipped default.""" + """Project file if present; else the included default.""" if project_data_folder is not None: candidate = Path(project_data_folder) / filename if candidate.is_file(): diff --git a/src/geckopy/databases/phyl_dist.py b/src/geckopy/databases/phyl_dist.py index 126ec0b..d8b486d 100644 --- a/src/geckopy/databases/phyl_dist.py +++ b/src/geckopy/databases/phyl_dist.py @@ -44,7 +44,7 @@ class PhylDist: ``dist_matrix[i, j]`` is the distance between ``names[i]`` and ``names[j]``. name_to_index - Lowercased name -> index in ``names``. First occurrence wins + Lowercased name -> index in ``names``. First occurrence is kept for duplicate names (matching MATLAB ``find(..., 1)``). genus_to_indices Lowercased genus (first whitespace-delimited word of the @@ -148,7 +148,7 @@ def download_phyl_dist( ) -> Path: """Download the KEGG phylogenetic distance file distributed with RAVEN. - ``fuzzy_kcat_matching`` uses it to pick the evolutionarily closest + ``fuzzy_kcat_matching`` uses it to select the evolutionarily closest organism when BRENDA has no entry for the model's own organism. The file covers all KEGG organisms (~5 MB). It is written to a temporary file next to ``out_path`` and only moved into place once diff --git a/src/geckopy/databases/pubchem.py b/src/geckopy/databases/pubchem.py index fc9ef0d..b2fe40e 100644 --- a/src/geckopy/databases/pubchem.py +++ b/src/geckopy/databases/pubchem.py @@ -47,8 +47,8 @@ def met_smiles(metabolite) -> str: cobra stores an annotation value as the object it was assigned, and returns a *list* for a model read back from a file: a metabolite - annotated in memory by :func:`find_met_smiles` carries ``"CCO"``, - and the same metabolite after a save/load round trip carries + annotated in memory by :func:`find_met_smiles` has ``"CCO"``, + and the same metabolite after it is saved and loaded has ``["CCO"]``. Readers that assume a string get a list that compares equal to nothing and is not hashable. diff --git a/src/geckopy/databases/uniprot_loader.py b/src/geckopy/databases/uniprot_loader.py index a9fd474..eeaa8ef 100644 --- a/src/geckopy/databases/uniprot_loader.py +++ b/src/geckopy/databases/uniprot_loader.py @@ -142,7 +142,7 @@ def load_uniprot_tsv( mw_da: list[float] = [] sequences: list[str] = [] # One Entry per source row (before gene-cell splitting), so duplicate - # detection sees each row exactly once regardless of how many genes it + # detection counts each row exactly once regardless of how many genes it # expands to. source_row_ids: list[str] = [] diff --git a/src/geckopy/ec_model/ec_model.py b/src/geckopy/ec_model/ec_model.py index 806b170..f2e58ea 100644 --- a/src/geckopy/ec_model/ec_model.py +++ b/src/geckopy/ec_model/ec_model.py @@ -4,7 +4,7 @@ so everything you know how to do with cobra still works: solve FBAs, edit reactions, inspect metabolites, etc. -It carries two extra attributes on top of cobra: +It has two extra attributes on top of cobra: - ``ec``: an ``EcData`` holding the enzyme-related arrays (kcats, enzyme MW, the reaction-enzyme coupling matrix, ...). @@ -114,7 +114,7 @@ def copy(self) -> "EcModel": """Copy the model, including an independent ``ec`` substructure. ``cobra.Model.copy`` rebuilds the reactions, metabolites and genes - but carries every other attribute over **by reference**, so without + but copies every other attribute **by reference**, so without this override ``model.copy().ec`` would be the same ``EcData`` object as ``model.ec``: writing a kcat on the copy would silently change the original. For the same reason the copy's ``enzymes`` diff --git a/src/geckopy/ec_model/enzyme.py b/src/geckopy/ec_model/enzyme.py index 1e6cddb..e82f11c 100644 --- a/src/geckopy/ec_model/enzyme.py +++ b/src/geckopy/ec_model/enzyme.py @@ -199,7 +199,7 @@ def reactions(self) -> frozenset: ``rxn_enz_mat[:, self.index]``, intersected with reactions still present in the cobra model. - For gecko-light models, each ec row carries a ``###_`` prefix + For gecko-light models, each ec row has a ``###_`` prefix identifying its isozyme; the prefix is stripped before the cobra lookup so two isozyme rows of the same cobra reaction return a single ``cobra.Reaction``. diff --git a/src/geckopy/ec_model/make_ec_model.py b/src/geckopy/ec_model/make_ec_model.py index 69f7d35..7147c4b 100644 --- a/src/geckopy/ec_model/make_ec_model.py +++ b/src/geckopy/ec_model/make_ec_model.py @@ -159,7 +159,7 @@ def make_ec_model( Pre-loaded KeggDB. Optional fallback source for protein information and EC numbers. When provided, stage 7 fills genes that UniProt missed (with ``ec.enzymes`` set to the - UniProt accession carried in the KEGG row, or to the KEGG + UniProt accession held in the KEGG row, or to the KEGG gene id when that accession is empty), and the EC-number lookup (``fill_eccodes_from_database``) consults KEGG when the UniProt result is empty or ends with ``-``. If None, the diff --git a/src/geckopy/ec_model/pipeline/apply_kcat.py b/src/geckopy/ec_model/pipeline/apply_kcat.py index 221a8d1..264ea03 100644 --- a/src/geckopy/ec_model/pipeline/apply_kcat.py +++ b/src/geckopy/ec_model/pipeline/apply_kcat.py @@ -4,7 +4,7 @@ Supports both the full and gecko-light formulations. The two share the clear-then-write idempotency contract but write to different metabolites: full models write per-enzyme ``prot_`` coefficients on each ec.rxn; -light models pick the lowest-cost isozyme per cobra reaction and write a +light models select the lowest-cost isozyme per cobra reaction and write a single ``prot_pool`` coefficient there. """ from __future__ import annotations @@ -268,7 +268,7 @@ def _apply_kcat_constraints_light( if selected_idx.size == 0: return - # The cobra reaction carries the LP constraint, so selecting one isozyme + # The cobra reaction has the LP constraint, so selecting one isozyme # row updates the reaction as a whole: every ec row of that reaction # competes for the cheapest cost, selected or not. affected = { diff --git a/src/geckopy/ec_model/pipeline/populate_ec.py b/src/geckopy/ec_model/pipeline/populate_ec.py index b05fc89..61939d5 100644 --- a/src/geckopy/ec_model/pipeline/populate_ec.py +++ b/src/geckopy/ec_model/pipeline/populate_ec.py @@ -195,7 +195,7 @@ def populate_enzyme_data( if kegg_idx is None: unmatched.append(gene) continue - # KEGG fallback hit. Prefer the UniProt accession carried on the + # KEGG fallback hit. Prefer the UniProt accession given in the # KEGG row; fall back to the bare KEGG gene id when empty. kegg_uniprot = kegg_db.uniprot_ids[kegg_idx] if kegg_uniprot: @@ -337,7 +337,7 @@ def allocate_ec_and_coupling_light(model: "EcModel") -> list[str]: into ec instead: - Each cobra reaction with N isozymes (i.e. ``len(gpr_to_dnf(rxn.gpr)) - == N``) produces N rows in ec.rxns. Row k carries the id + == N``) produces N rows in ec.rxns. Row k has the id ``"{k:03d}_{rxn.id}"`` (1-indexed, three-digit zero-padded counter). - Row k's slice of ``ec.rxn_enz_mat`` is 1.0 for each gene in that isozyme's AND-clause; other genes are 0. diff --git a/src/geckopy/ec_model/pipeline/set_kcat.py b/src/geckopy/ec_model/pipeline/set_kcat.py index d20db97..7f3a4f5 100644 --- a/src/geckopy/ec_model/pipeline/set_kcat.py +++ b/src/geckopy/ec_model/pipeline/set_kcat.py @@ -35,7 +35,7 @@ def set_kcat_for_reactions( - Any other ID matches every ec.rxns entry whose ID, after removing a trailing ``_EXP_``, equals it. So in a full model ``"R2"`` matches ``R2``, ``R2_EXP_1`` and ``R2_EXP_2``. In a gecko-light - model the ec.rxns IDs carry a ``###_`` prefix (``001_R2``), which + model the ec.rxns IDs have a ``###_`` prefix (``001_R2``), which is part of the ID: ``"R2"`` matches nothing there and ``"001_R2"`` matches that one row. - An ID that matches nothing is ignored. diff --git a/src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py b/src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py index 573bbc2..6c9a588 100644 --- a/src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py +++ b/src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py @@ -108,7 +108,7 @@ def fuzzy_kcat_matching( * any organism + SA (output origin 6) When the EC has any wildcard, substrate-matching levels are - skipped (the substrate field of an EC carries the catalytic + skipped (the substrate field of an EC holds the catalytic specificity that wildcards already discard). Across the multiple EC tokens of one reaction, the best match is @@ -118,14 +118,14 @@ def fuzzy_kcat_matching( The model's organism is read from ``model.adapter.params.org_name``. When BRENDA has no exact - organism match, ``phyl_dist`` is used to pick the BRENDA row(s) + organism match, ``phyl_dist`` is used to select the BRENDA row(s) for the phylogenetically closest organism(s) (with genus fallback for organisms missing from KEGG). The six levels are tried in the fixed order listed above, not in order of output rank: org + SA (origin 5) is tried before any organism + any substrate + KCAT (origin 4), so when both would - match, org + SA wins even though origin 4 would otherwise be + match, org + SA is kept even though origin 4 would otherwise be considered the better rank. Match statistics (counts per origin and per wildcard level) are @@ -157,7 +157,7 @@ def fuzzy_kcat_matching( ``model.adapter.params.kcat_aggregate_brenda``, which itself defaults to ``"max"``. - The choice drives both aggregation layers consistently: it picks + The choice sets both aggregation layers consistently: it selects the matching snapshot view (``brenda.kcat_for(aggregate)`` / ``brenda.sa_for(aggregate)``) and applies the same reduction across whichever rows survive the EC + organism + substrate @@ -221,7 +221,7 @@ def fuzzy_kcat_matching( return _empty_result_df() # The snapshot stores both max and median aggregations per - # (ec, substrate, organism) triple; pick the view that matches the + # (ec, substrate, organism) triple; select the view that matches the # requested per-query aggregation so both layers are consistent. brenda_kcat = brenda.kcat_for(aggregate) brenda_sa = brenda.sa_for(aggregate) diff --git a/src/geckopy/gather_kcats/fuzzy_kcat_matching/_brenda_query.py b/src/geckopy/gather_kcats/fuzzy_kcat_matching/_brenda_query.py index 37af615..276a7c5 100644 --- a/src/geckopy/gather_kcats/fuzzy_kcat_matching/_brenda_query.py +++ b/src/geckopy/gather_kcats/fuzzy_kcat_matching/_brenda_query.py @@ -2,7 +2,7 @@ Given an EC token (possibly with wildcards), a list of substrates, and the BRENDA kcat/SA tables, these helpers find candidate rows, -filter them, pick a kcat, and (for the iterative version) escalate +filter them, select a kcat, and (for the iterative version) escalate wildcards on misses. The six MATLAB search levels (org+subs+kcat, any+subs+kcat, @@ -30,8 +30,8 @@ # above this are clipped, matching MATLAB. DIFFUSION_LIMIT = 1e7 -# "No match" wildcard count; a large value that never wins -# min-comparisons against real wildcard counts. +# "No match" wildcard count; a large value that is never the smaller one +# in comparisons with real wildcard counts. NO_MATCH_WC = 1000 @@ -151,7 +151,7 @@ def match_kcat( collapsed to one value. Returns ``(kcat_in_1_per_s, num_matched_rows)``, where ``kcat`` is - the value picked by ``aggregate``. ``num_matched == 0`` means no + the value selected by ``aggregate``. ``num_matched == 0`` means no rows survived the filters. """ rows = find_ec_rows(ec_token, ec_indices) diff --git a/src/geckopy/gather_kcats/get_standard_kcat.py b/src/geckopy/gather_kcats/get_standard_kcat.py index d8760f0..c9f9ddf 100644 --- a/src/geckopy/gather_kcats/get_standard_kcat.py +++ b/src/geckopy/gather_kcats/get_standard_kcat.py @@ -163,7 +163,7 @@ def _first_subsystem(rxn: cobra.Reaction) -> str: """Return the first subsystem token (split on `;`) or empty string. Tolerates both string (MATLAB-style ``;``-joined) and list - (cobra-py YAML round-trip) representations of ``rxn.subsystem``. + (cobra-py YAML written and read back) representations of ``rxn.subsystem``. """ sub = rxn.subsystem if not sub: diff --git a/src/geckopy/gather_kcats/kcat_assignment.py b/src/geckopy/gather_kcats/kcat_assignment.py index ad6108e..5d4189c 100644 --- a/src/geckopy/gather_kcats/kcat_assignment.py +++ b/src/geckopy/gather_kcats/kcat_assignment.py @@ -1,6 +1,6 @@ """Assign kcats from the kcat source files, as model_adapter.toml sets. -``collect_kcats`` decides one kcat per reaction from the source files in +``collect_kcats`` selects one kcat per reaction from the source files in ``data/kcats`` without changing the model; ``assign_kcats`` applies the decisions; ``update_kcat_sources`` gathers the source files the configuration needs. GECKO (MATLAB) has the same functions as @@ -299,7 +299,7 @@ def collect_kcats( :func:`assign_kcats` applies the decisions. The order is a preset or ``[[kcat_assignment.stage]]`` tables. A stage - decides the reactions that are still open: + assigns a kcat to the reactions that are still open: - ``select``: the first listed source with a value (see :func:`merge_kcats`). Sources are source file names and the BRENDA diff --git a/src/geckopy/gather_kcats/merge_dlkcat_and_fuzzy_kcats.py b/src/geckopy/gather_kcats/merge_dlkcat_and_fuzzy_kcats.py index d3c975c..86c129d 100644 --- a/src/geckopy/gather_kcats/merge_dlkcat_and_fuzzy_kcats.py +++ b/src/geckopy/gather_kcats/merge_dlkcat_and_fuzzy_kcats.py @@ -2,7 +2,7 @@ Thin backward-compatible wrapper around :func:`merge_kcats`, kept for the common DLKcat + fuzzy-BRENDA case and for parity with the MATLAB name. -New code should call :func:`merge_kcats` directly, which also handles +New code should call :func:`merge_kcats` directly, which also accepts single lists that mix several sources (e.g. an OpenKineticsPredictor result with BRENDA / Sabio-RK / CataPro rows). diff --git a/src/geckopy/gather_kcats/merge_kcats.py b/src/geckopy/gather_kcats/merge_kcats.py index 860a89d..7c3b525 100644 --- a/src/geckopy/gather_kcats/merge_kcats.py +++ b/src/geckopy/gather_kcats/merge_kcats.py @@ -4,15 +4,15 @@ fixed inputs (DLKcat + fuzzy BRENDA), :func:`merge_kcats` accepts any number of kcat lists, each of which may itself mix several sources. An OpenKineticsPredictor result, for example, is a single list whose rows -carry ``BRENDA``, ``Sabio-RK``, ``CataPro`` or ``UniProt`` in their -``source`` column. A caller-supplied ``source_priority`` decides which -source wins for each reaction. +contain ``BRENDA``, ``Sabio-RK``, ``CataPro`` or ``UniProt`` in their +``source`` column. A caller-supplied ``source_priority`` determines which +source is kept for each reaction. Three generic tier tokens may appear in ``source_priority`` instead of a literal source name: - ``"database_exact"`` -- an exact experimental-database measurement: an - experimental-database row (``BRENDA`` / ``Sabio-RK``) that carries no + experimental-database row (``BRENDA`` / ``Sabio-RK``) that has no fuzzy metadata, i.e. a direct OKP hit with no EC wildcards. These are the most precise values and are usually ranked first. - ``"database_top"`` -- a high-confidence *fuzzy* BRENDA match with @@ -23,7 +23,7 @@ Anything else in ``source_priority`` is matched against the row's ``source`` after normalisation to lowercase ``snake_case`` (so ``"DLKcat"`` matches ``"dlkcat"`` and ``"Sabio-RK"`` matches -``"sabio_rk"``). A row whose source carries the ``OKP-`` pipeline-stage +``"sabio_rk"``). A row whose source contains the ``OKP-`` pipeline-stage tag (``parse_okp_output``'s output, e.g. ``"OKP-BRENDA"``) is matched and tiered by its underlying source with the tag stripped -- an ``OKP-BRENDA`` row still routes to the database tiers like a bare @@ -103,11 +103,11 @@ def merge_kcats( All input lists are concatenated and each row is assigned a tier (see the module docstring). For every reaction the highest-priority - tier present wins, and **all** rows of that tier for that reaction + tier present is kept, and **all** rows of that tier for that reaction are kept; rows of lower-priority tiers for the same reaction are dropped. - Rows whose ``kcat`` is non-positive or missing carry no usable + Rows whose ``kcat`` is non-positive or missing have no usable turnover number and are dropped first. This also removes the ``kcat == 0`` / ``origin == NA`` rows that ``fuzzy_kcat_matching`` emits for unmatched reactions, so a leftover NA-metadata diff --git a/src/geckopy/gather_kcats/select_kcat_value.py b/src/geckopy/gather_kcats/select_kcat_value.py index 47bd3dd..1e54234 100644 --- a/src/geckopy/gather_kcats/select_kcat_value.py +++ b/src/geckopy/gather_kcats/select_kcat_value.py @@ -66,7 +66,7 @@ def apply_kcat_list( The contributing entry's provenance is recorded in ``model.ec.source`` via :func:`format_kcat_source`: a lowercase string whose first token is the source and whose bracketed detail, - for a fuzzy BRENDA match, carries the wildcard level and origin + for a fuzzy BRENDA match, contains the wildcard level and origin (e.g. ``"brenda (wc=0, origin=1)"``). If the list has no ``wildcard_level`` / ``origin`` columns the source is the bare token. diff --git a/src/geckopy/get_enzyme_data/find_ec_in_db.py b/src/geckopy/get_enzyme_data/find_ec_in_db.py index 595cbbf..ee7c9bb 100644 --- a/src/geckopy/get_enzyme_data/find_ec_in_db.py +++ b/src/geckopy/get_enzyme_data/find_ec_in_db.py @@ -114,9 +114,9 @@ def find_ec_in_db( ``gene_to_protein_indices``. Missing keys count as no DB match. * Filter to proteins with non-empty ``db_eccodes``. * If those proteins agree on a single EC string (or there is only - one protein), pick the lightest-MW one's string. NaN MWs are + one protein), select the lightest-MW one's string. NaN MWs are treated as +inf so a NaN-only group falls back to the first. - * If they disagree, pick the first distinct string in DB order + * If they disagree, select the first distinct string in DB order (matching MATLAB ``unique('stable')``) and emit a ``logger.warning`` listing the gene as a conflict. diff --git a/src/geckopy/kcat_tuning/corrections.py b/src/geckopy/kcat_tuning/corrections.py index 79ed417..4f9251a 100644 --- a/src/geckopy/kcat_tuning/corrections.py +++ b/src/geckopy/kcat_tuning/corrections.py @@ -22,7 +22,7 @@ import numpy as np # Isozyme copies of one reaction are stored as separate ec rows with a -# suffix; the underlying reaction carries the name. +# suffix; the underlying reaction has the name. _ISOZYME_SUFFIX = "_EXP_" @@ -144,7 +144,7 @@ def corrections_tsv(rows: Sequence[Correction]) -> str: def annotate_from_model(model, rxn_ids: Sequence[str]) -> dict[str, list[str]]: """Names, EC codes and sources for ``rxn_ids``, read off an ecModel. - An isozyme copy carries a ``_EXP_n`` suffix that the underlying + An isozyme copy has a ``_EXP_n`` suffix that the underlying reaction does not, so the name is looked up under the stripped id. Anything absent is reported as ``""``. """ diff --git a/src/geckopy/kcat_tuning/evotune/distance.py b/src/geckopy/kcat_tuning/evotune/distance.py index ee84195..4594471 100644 --- a/src/geckopy/kcat_tuning/evotune/distance.py +++ b/src/geckopy/kcat_tuning/evotune/distance.py @@ -202,7 +202,7 @@ def evotune_distance( max_growth_weight Relative weight of the max-growth dataset against the flux dataset, giving ``(rmse_flux + w * rmse_max_grate) / (w + 1)``. - At 2 the eight max-growth conditions carry twice the weight of + At 2 the eight max-growth conditions have twice the weight of the 33 flux conditions, which is the intended emphasis here: the max-growth set is where the carbon-source diversity lives, while 30 of the 33 flux conditions are glucose. diff --git a/src/geckopy/kcat_tuning/evotune/mask_size.py b/src/geckopy/kcat_tuning/evotune/mask_size.py index 9d9afd8..65cd6d7 100644 --- a/src/geckopy/kcat_tuning/evotune/mask_size.py +++ b/src/geckopy/kcat_tuning/evotune/mask_size.py @@ -3,7 +3,7 @@ :func:`~.tuning.select_tunable_mask`'s ``target_impact_share`` selects a comparable *share* of leverage on any model -- that is the point of a relative cutoff over a fixed count or a fixed leverage value. It does not -follow that the same share sits at a comparable point on the *fit* +follow that the same share is at a comparable point on the *fit* curve: how concentrated a model's leverage is, and how quickly its fit stops improving as more parameters join the search, are different properties of the model and the data, and they do not move together. @@ -17,7 +17,7 @@ The two functions here are meant to be used together but stay separate: :func:`sweep_tunable_mask_size` only measures (it runs :func:`~.tuning.cmaes_kcat_tuning` at each requested share and reports -what came back); :func:`recommend_target_impact_share` only decides, +what came back); :func:`recommend_target_impact_share` only recommends, from measurements already in hand. Passing a sweep's own points through the second lets a different stopping rule be tried without re-tuning. """ @@ -84,7 +84,7 @@ def rmse_mean(self) -> float: @property def rmse_sd(self) -> float: """Sample standard deviation across seeds; ``nan`` with fewer - than two -- a single seed carries no information about noise, + than two -- a single seed gives no information about noise, and pretending otherwise (e.g. reporting 0) understates it.""" return float(np.std(self.rmse, ddof=1)) if len(self.rmse) > 1 else float("nan") @@ -122,7 +122,7 @@ def sweep_tunable_mask_size( The defaults are a coarse scout, not a guarantee of finding the true optimum: five or six points cannot resolve a curve whose real - knee sits inside one of the gaps between them, and how wide that + knee lies inside one of the gaps between them, and how wide that gap needs to be to matter is exactly what a first sweep is for finding out. Look at ``n_selected`` after a first sweep -- if consecutive shares jump by a large multiple in parameter count @@ -143,7 +143,7 @@ def sweep_tunable_mask_size( cheapest possible scout of the shape; widen to more once a promising region is known, since that is where seed noise is most likely to matter (fit values close enough together for it to - decide the comparison). + determine the result of the comparison). Parameters ---------- @@ -213,7 +213,7 @@ def _local_sd(a: MaskSizePoint, b: MaskSizePoint) -> Optional[float]: land on different local optima across seeds), and borrowing an unrelated point's spread to judge this pair would let noise from somewhere else in the sweep, that has nothing to do with this - specific comparison, decide it.""" + specific comparison, determine its result.""" sds = [p.rmse_sd for p in (a, b) if not np.isnan(p.rmse_sd)] if not sds: return None diff --git a/src/geckopy/kcat_tuning/evotune/parsimony.py b/src/geckopy/kcat_tuning/evotune/parsimony.py index 3da3b74..99f42e5 100644 --- a/src/geckopy/kcat_tuning/evotune/parsimony.py +++ b/src/geckopy/kcat_tuning/evotune/parsimony.py @@ -58,9 +58,9 @@ def source_movement( Two medians are reported per source, separating how *many* kcats a result moves from how *far* it moves them. ``median_fold`` is taken - over every kcat of the source; where most of a source sits at its + over every kcat of the source; where most of a source is at its prior -- which a restrictive ``tunable_mask`` guarantees -- it - saturates at 1.0 and carries no information about the tier. + saturates at 1.0 and gives no information about the tier. ``median_fold_moved`` conditions on the kcats that did move, and so stays informative at any sparsity. @@ -112,16 +112,16 @@ def impact_share( drmse: np.ndarray, rel_tol: float = 0.02, ) -> float: - """Fraction of the total screened impact carried by changed kcats. + """Fraction of the total screened impact that the changed kcats account for. ``drmse`` is each kcat's one-at-a-time effect on the distance. A result that changes many parameters the data barely responds to scores low here even when it changes few of them; one that changes a handful of load-bearing parameters scores high. Distinguishes "few, large, and consequential" from mere sparsity -- on the full - ecYeastGEM run the shrunk blend changes 560 kcats but carries only + ecYeastGEM run the shrunk blend changes 560 kcats but accounts for only 28% of the available impact, while 301 changes chosen by the screen - carry 69%. + account for 69%. """ drmse = np.asarray(drmse, dtype=float) total = drmse.sum() @@ -146,7 +146,7 @@ def identifiability_mask( measured effect of an unlabelled one to qualify. This uses only the *ranking* of the sigmas across sources, which is - the part that carries meaning -- their absolute values are a + the part that has meaning -- their absolute values are a convention. One global ``threshold`` sets the overall strictness; the per-source differentiation is derived, not tuned. """ @@ -239,7 +239,7 @@ def best_parsimonious( Defaults to 2%, which is the scale of the alternate-optimum noise measured on this model -- below that, two vectors are not - meaningfully different in fit, so the sparser one wins. A point with + meaningfully different in fit, so the sparser one is selected. A point with a NaN RMSE is never chosen and does not set the best RMSE; when every RMSE is NaN no point qualifies and ``ValueError`` is raised. """ diff --git a/src/geckopy/kcat_tuning/evotune/tuning.py b/src/geckopy/kcat_tuning/evotune/tuning.py index 3d68b09..978b3fc 100644 --- a/src/geckopy/kcat_tuning/evotune/tuning.py +++ b/src/geckopy/kcat_tuning/evotune/tuning.py @@ -430,7 +430,7 @@ def cmaes_kcat_tuning( ---------- model EcModel with a populated ``ec.kcat``. Mutated in place: on - return, tunable rows carry the best kcat vector CMA-ES found, + return, tunable rows hold the best kcat vector CMA-ES found, with kcat constraints already applied. tunable_mask, screen, target_impact_share Control which kcats are searched over, in order of precedence. @@ -631,7 +631,7 @@ def tune_prior_penalty_weight( verbose: bool = True, ) -> pd.DataFrame: """Sweep ``prior_penalty_weight`` and report the fit/reproducibility - trade-off, so the value doesn't have to be picked blind. + trade-off, so the value need not be selected without information. ``prior_penalty_weight`` charges for moving a kcat away from its prior, and mainly exists to keep large corrections reproducible @@ -882,7 +882,7 @@ def _score_kcat_vector( max_growth_weight=max_growth_weight, ) # The search may run on a penalised objective, but the plain RMSE - # is always carried alongside it so a run stays comparable to + # is always recorded alongside it so a run stays comparable to # MATLAB's and to runs at other penalties. objective = rmse if prior_penalty_weight and kcat0 is not None and sigma0_log is not None: diff --git a/src/geckopy/kcat_tuning/evotune/tying.py b/src/geckopy/kcat_tuning/evotune/tying.py index c8fc89f..f82714a 100644 --- a/src/geckopy/kcat_tuning/evotune/tying.py +++ b/src/geckopy/kcat_tuning/evotune/tying.py @@ -24,7 +24,7 @@ import numpy as np -# Isozyme copies of a reaction carry this suffix; the text before it is +# Isozyme copies of a reaction have this suffix; the text before it is # the reaction they share. ISOZYME_SUFFIX = "_EXP_" diff --git a/src/geckopy/kcat_tuning/sensitivity_tuning.py b/src/geckopy/kcat_tuning/sensitivity_tuning.py index 5443119..aba983e 100644 --- a/src/geckopy/kcat_tuning/sensitivity_tuning.py +++ b/src/geckopy/kcat_tuning/sensitivity_tuning.py @@ -118,7 +118,7 @@ def corrections( leverage of its own to offer -- every bump was, by construction, the bottleneck at the time it was made -- so without ``leverage`` rows fall back to ordering by fold change. - ``rxn_names`` already carries resolved names, so there is no + ``rxn_names`` already contains resolved names, so there is no need for ``annotate_from_model`` here. """ return build_corrections( @@ -147,7 +147,7 @@ def sensitivity_tuning( 2. If growth didn't increase since the last iteration, warns and breaks (likely a substrate / pool constraint, not a kcat one). 3. If growth reached the target, returns. - 4. Otherwise picks the ``usage_prot_*`` reaction with the highest + 4. Otherwise selects the ``usage_prot_*`` reaction with the highest flux (= most-produced enzyme), excluding any whose UniProt ID is in ``prot_to_ignore``. 5. Finds the catalysed reaction consuming the most of that @@ -158,7 +158,7 @@ def sensitivity_tuning( ``"sensitivityTuning"``. 7. Re-applies kcat constraints for that single reaction. - On a gecko-light model each iteration instead picks the reaction with + On a gecko-light model each iteration instead selects the reaction with the most flux through the protein pool, multiplies the kcat of all its isozyme rows by ``fold_change`` and re-applies the constraint of that reaction; ``ec.notes`` and ``ec.source`` are left as they are. @@ -177,7 +177,7 @@ def sensitivity_tuning( fold_change Per-iteration kcat multiplier. prot_to_ignore - UniProt IDs whose usage rxns are excluded from being picked + UniProt IDs whose usage rxns are excluded from being selected as the limiting enzyme, matched without regard to case. An ID with no usage reaction raises ``ValueError`` on a full model; on a gecko-light model it is ignored. @@ -336,7 +336,7 @@ def sensitivity_tuning( # Find the catalysed rxn consuming the most of this protein. # Consumption: S[prot, rxn] < 0 with positive flux -> negative product. - # We pick the rxn with the most-negative S[prot] * flux. + # The rxn with the most-negative S[prot] * flux is selected. target_rxn = None target_value = 0.0 for rxn in prot_met.reactions: diff --git a/src/geckopy/kcat_tuning/sigma_fitter.py b/src/geckopy/kcat_tuning/sigma_fitter.py index 44ca07b..3cf176f 100644 --- a/src/geckopy/kcat_tuning/sigma_fitter.py +++ b/src/geckopy/kcat_tuning/sigma_fitter.py @@ -8,7 +8,7 @@ The algorithm: try a range of sigma values, set the protein-pool bound at each, solve the model, compare the predicted growth -rate to the experimental one, pick the sigma that minimises the +rate to the experimental one, select the sigma that minimises the difference. The default scan is the 0.01 grid of GECKO's ``sigmaFitter``; the objective reaction's bounds are opened to ``[0, 1000]`` first, and stay open on the fitted model. @@ -98,14 +98,14 @@ def fit_sigma( n_sigma_steps: int = 100, method: Literal["grid", "bisect"] = "grid", ) -> SigmaFitterResult: - """Sweep sigma and pick the one matching ``growth_rate`` best. + """Sweep sigma and select the one matching ``growth_rate`` best. ``method="grid"`` (default) is the sweep of GECKO's ``sigmaFitter``: sigma ``i / n`` for ``i = 1..n`` (every grid point evaluated), and the - first grid point with the smallest error wins. For a target that growth + first grid point with the smallest error is kept. For a target that growth cannot reach this is the first sigma of the growth plateau when the plateau values are identical; values that differ by solver noise can move - the pick to a later grid point, as in ``sigmaFitter.m``. A model that + the selection to a later grid point, as in ``sigmaFitter.m``. A model that cannot grow at any sigma gives the first grid point, ``1 / n_sigma_steps``. ``method="bisect"`` exploits that growth is monotone non-decreasing in sigma and finds the best sigma, to a resolution of 1e-6 and not on the diff --git a/src/geckopy/limit_proteins/constrain_flux_data.py b/src/geckopy/limit_proteins/constrain_flux_data.py index 34d4e01..9aa3dc5 100644 --- a/src/geckopy/limit_proteins/constrain_flux_data.py +++ b/src/geckopy/limit_proteins/constrain_flux_data.py @@ -3,7 +3,7 @@ In a typical chemostat experiment you measure exchange fluxes (substrate uptake, byproduct secretion, biomass growth rate) under one or more growth conditions. This function takes a -``FluxData`` (parsed from ``fluxData.tsv``), picks one condition, +``FluxData`` (parsed from ``fluxData.tsv``), selects one condition, and applies those measurements as bounds on the matching exchange reactions in the model. @@ -43,7 +43,7 @@ def apply_flux_data_constraints( Sets the biomass reaction bounds to the measured growth rate and each exchange reaction in ``flux_data.exch_rxn_ids`` to its measured flux for the chosen condition. Also zeros out the - adapter's preferred carbon source so the data drives the choice. + adapter's preferred carbon source so the data determines the choice. Ported from GECKO MATLAB: src/geckomat/limit_proteins/constrainFluxData.m. diff --git a/src/geckopy/limit_proteins/flexibilize_enz_concs.py b/src/geckopy/limit_proteins/flexibilize_enz_concs.py index 73b68d2..344cccd 100644 --- a/src/geckopy/limit_proteins/flexibilize_enz_concs.py +++ b/src/geckopy/limit_proteins/flexibilize_enz_concs.py @@ -7,7 +7,7 @@ ``flexibilize_enz_concs`` fixes that automatically. It computes a control coefficient per enzyme (how much each enzyme's bound is -limiting growth), picks the worst one, multiplies its upper bound +limiting growth), selects the worst one, multiplies its upper bound by ``fold_change``, re-solves, and repeats until growth reaches the target. The result records which enzymes were loosened and by how much, so a curator can review. @@ -17,7 +17,7 @@ coefficient, relaxes gradually, and runs a tighten-back pass, so it yields the *minimal* (most proteomics-faithful) relaxation but is slower. ``relax_proteomics_greedy`` ranks by shadow price and fully -unconstrains each picked enzyme — faster, but looser. See +unconstrains each selected enzyme, which is faster but looser. See ``docs/relaxation_methods.md`` for the full comparison. Ported from GECKO MATLAB: @@ -86,7 +86,7 @@ class FlexEnzResult: ratio_incr ``flex_concs / old_concs``; ``inf`` where ``old_concs`` is 0. frequence - Number of loop iterations in which each enzyme was picked as + Number of loop iterations in which each enzyme was selected as the most limiting one. """ @@ -153,10 +153,10 @@ def flexibilize_enz_concs( ``model.adapter.params.gr_exp``. fold_change Controls how much the limiting enzyme's usage upper bound - grows each time it is picked: on its ``k``-th pick, the + grows each time it is selected: on its ``k``-th selection, the bound is set to ``original_conc * (1 + fold_change * k)``. iter_per_enzyme - Maximum number of times the same enzyme may be picked before + Maximum number of times the same enzyme may be selected before warning and breaking. ``0`` means no limit. bio_rxn Biomass reaction id. Defaults to ``params.bio_rxn`` from the @@ -168,7 +168,7 @@ def flexibilize_enz_concs( rank enzymes and complexes by the growth gained per fold of raising all their caps together (a levelled complex binds on all subunits at once, so no single subunit shows a gain), raise every - measured subunit of the picked complex by the same factor, count + measured subunit of the selected complex by the same factor, count iterations per complex, and keep one factor per complex in the refinement pass, so that concentrations levelled by ``level_complex_concs`` stay levelled. The ``frequence`` of a @@ -545,8 +545,8 @@ def _unit_scores( if (scores > 0).any(): return scores scores = np.zeros(len(units)) - # No dual singles a unit out (none exposed, or the protein pool binds as - # well and carries the whole price): probe each candidate with the pool + # No dual identifies a unit (none exposed, or the protein pool binds as + # well and accounts for the whole price): probe each candidate with the pool # lifted, so the probe finds the caps that limit once the pool is relaxed. pool_rxn = model.reactions.get_by_id(POOL_EXCHANGE_ID) with model: diff --git a/src/geckopy/limit_proteins/get_conc_control_coeffs.py b/src/geckopy/limit_proteins/get_conc_control_coeffs.py index 9b09fda..ab05192 100644 --- a/src/geckopy/limit_proteins/get_conc_control_coeffs.py +++ b/src/geckopy/limit_proteins/get_conc_control_coeffs.py @@ -203,7 +203,7 @@ def _forward_reduced_cost(rxn, flux: Optional[float] = None) -> Optional[float]: scipy path needs it to confirm that the stored result is that solution. ``reaction.reduced_cost`` is the forward variable's value minus the reverse - variable's, and the reverse variable of a usage reaction carries the opposite + variable's, and the reverse variable of a usage reaction has the opposite sign, so it is twice the sensitivity to the upper bound; the forward variable alone is that sensitivity. """ diff --git a/src/geckopy/limit_proteins/relax_enz_concs.py b/src/geckopy/limit_proteins/relax_enz_concs.py index a700fbd..703eea8 100644 --- a/src/geckopy/limit_proteins/relax_enz_concs.py +++ b/src/geckopy/limit_proteins/relax_enz_concs.py @@ -7,7 +7,7 @@ with noisy proteomics, small changes to that ranking can change the result. ``relax_enz_concs`` asks the question once instead: what is the smallest set of fold increases of the measured caps (and, if necessary, -of the protein pool) that lets the model reach the target growth rate? +of the protein pool) that allows the model to reach the target growth rate? Every measured enzyme's cap becomes ``usage <= conc * (1 + x)`` and the sum of the ``x`` is minimised under ``growth >= exp_growth``. The protein diff --git a/src/geckopy/limit_proteins/relax_proteomics_greedy.py b/src/geckopy/limit_proteins/relax_proteomics_greedy.py index 1c30f80..0441021 100644 --- a/src/geckopy/limit_proteins/relax_proteomics_greedy.py +++ b/src/geckopy/limit_proteins/relax_proteomics_greedy.py @@ -15,7 +15,7 @@ one is shadow-price-ordered (purely LP-driven, one solve per step) and often converges in fewer iterations when one or two enzymes dominate the infeasibility. The trade-off is that it fully -unconstrains each picked enzyme with no tighten-back pass, so the +unconstrains each selected enzyme with no tighten-back pass, so the result is looser (less proteomics-faithful) than ``flexibilize_enz_concs``. See ``docs/relaxation_methods.md`` for the full comparison. diff --git a/src/geckopy/solver_support.py b/src/geckopy/solver_support.py index 88d7707..50937e8 100644 --- a/src/geckopy/solver_support.py +++ b/src/geckopy/solver_support.py @@ -1,7 +1,7 @@ """Support for optlang's scipy (HiGHS) solver interface. The scipy interface of optlang differs from the other interfaces in ways that matter for an -ecModel, and this module handles them. +ecModel, and this module works around them. Index map after a removal ``scipy_interface.Problem`` keeps a map from variable name to column. After a variable is @@ -41,7 +41,7 @@ After a solve that finds no optimum (infeasible, unbounded) the scipy interface returns ``None`` for every primal value and raises for the objective, so ``cobra.Model.optimize`` fails with a ``TypeError`` instead of returning a solution with that status. The subclass - returns NaN for both, so the solution carries the status and NaN values. + returns NaN for both, so the solution has the status and NaN values. Duals The scipy interface raises for ``Constraint.dual`` and ``Variable.dual`` but keeps the @@ -64,7 +64,7 @@ from optlang import scipy_interface as _scipy_interface from optlang.util import SolverTolerances from scipy.optimize import linprog -except ImportError: # pragma: no cover - optlang always ships the interface +except ImportError: # pragma: no cover - optlang always includes the interface _scipy_interface = None __all__ = [ diff --git a/src/geckopy/upgrade.py b/src/geckopy/upgrade.py index 2ef4d6c..70cb12b 100644 --- a/src/geckopy/upgrade.py +++ b/src/geckopy/upgrade.py @@ -53,7 +53,7 @@ def convert_custom_kcats( The kcats are those :func:`~geckopy.ec_model.pipeline.apply_custom_kcats` gives the ecModel, including which of two entries for the same reaction - wins: one row per reaction, keyed by conventional reaction and enzyme set + is kept: one row per reaction, keyed by conventional reaction and enzyme set (see :func:`~geckopy.gather_kcats.ec_rxn_keys`), with the entry's notes in the note column. The ``stoicho`` column is left out: complex stoichiometry comes from ``apply_complex_data``. Unlike diff --git a/src/geckopy/utilities/ec_fseof.py b/src/geckopy/utilities/ec_fseof.py index 2ba1283..6b09bea 100644 --- a/src/geckopy/utilities/ec_fseof.py +++ b/src/geckopy/utilities/ec_fseof.py @@ -182,7 +182,7 @@ def _biomass_optimal_target_flux( """The production-target reaction's flux at biomass-optimum. Matches ``ecFSEOF.m``'s ``iniTarget``: the scan's floor is where the - model actually sits before any production is enforced, which can be + model actually is before any production is enforced, which can be near zero for a target with no baseline flux -- not a fixed fraction of the theoretical maximum. """ diff --git a/src/geckopy/utilities/ec_fva.py b/src/geckopy/utilities/ec_fva.py index f51e520..dae1d04 100644 --- a/src/geckopy/utilities/ec_fva.py +++ b/src/geckopy/utilities/ec_fva.py @@ -54,7 +54,7 @@ def tqdm(it, **_kwargs): # type: ignore[no-redef] def _init_worker(model: "EcModel") -> None: """Pool initializer: store one EcModel copy per worker process. - ``model`` arrives already deserialised -- ``ProcessPool`` handles + ``model`` arrives already deserialised -- ``ProcessPool`` does the serialisation itself, including a Windows-specific performance workaround (opencobra/cobrapy#997), so it's passed as-is rather than pre-pickled. @@ -167,7 +167,7 @@ def ec_fva( # Parallel path via cobra.util.ProcessPool -- the same primitive # cobrapy's own flux_variability_analysis/single_reaction_deletion # use for "repeated FBA on slightly-perturbed model copies, in - # parallel". It handles serialising ec_model to each worker + # parallel". It serialises ec_model for each worker # (including the Windows-specific performance workaround from # opencobra/cobrapy#997) and tears the pool down gracefully # (close()+join(), not an abrupt terminate()) on exit. diff --git a/src/geckopy/utilities/enzyme_usage.py b/src/geckopy/utilities/enzyme_usage.py index 7080988..1342bef 100644 --- a/src/geckopy/utilities/enzyme_usage.py +++ b/src/geckopy/utilities/enzyme_usage.py @@ -41,7 +41,7 @@ class EnzymeUsageResult: ub Upper bound of each ``usage_prot_`` reaction. fluxes - The original input flux distribution (carried through for + The original input flux distribution (kept unchanged for downstream use, e.g. by ``report_enzyme_usage``). measured_protein Sum of the measured concentrations in ``model.ec.concs`` (mg/gDCW) diff --git a/src/geckopy/utilities/get_subset_ec_model.py b/src/geckopy/utilities/get_subset_ec_model.py index 3e2c019..010f6c3 100644 --- a/src/geckopy/utilities/get_subset_ec_model.py +++ b/src/geckopy/utilities/get_subset_ec_model.py @@ -66,7 +66,7 @@ def get_subset_ec_model( MATLAB's ``removeGenes`` and ``removeReactions`` do. 7. Trim the corresponding rows of ``model.ec.rxns/kcat/source/notes/eccodes/rxn_enz_mat``. In a - gecko-light model the ec reaction ids carry a ``001_`` prefix that + gecko-light model the ec reaction ids have a ``001_`` prefix that is not part of the reaction id. Ported from GECKO MATLAB: @@ -74,8 +74,8 @@ def get_subset_ec_model( If any ``usage_prot_*`` reaction's upper bound differs from the default (1000), a warning is logged: protein-concentration - constraints on ``big_ec_model`` may not carry over meaningfully - to the subset. + constraints on ``big_ec_model`` may not remain meaningful + for the subset. Parameters ---------- @@ -127,7 +127,7 @@ def get_subset_ec_model( break # --- 3. Copy the model --- - # cobra.Model.copy() doesn't know about the `ec` substructure, so + # cobra.Model.copy() does not treat the `ec` substructure specially, so # deep-copy it explicitly to avoid mutating the input model. small = big_ec_model.copy() small.ec = copy.deepcopy(big_ec_model.ec) @@ -176,7 +176,7 @@ def get_subset_ec_model( # Drop the protein-pool machinery (usage_prot_ reaction and # prot_ metabolite) for accessions no longer used by any kept - # enzyme row, so the subset carries no orphaned enzyme constraints. + # enzyme row, so the subset contains no orphaned enzyme constraints. # An accession shared by a kept gene (duplicate accession) is # retained. kept_accessions = set(small.ec.enzymes) diff --git a/src/geckopy/utilities/load_conventional_gem.py b/src/geckopy/utilities/load_conventional_gem.py index f89c0b2..68d90d3 100644 --- a/src/geckopy/utilities/load_conventional_gem.py +++ b/src/geckopy/utilities/load_conventional_gem.py @@ -5,7 +5,7 @@ ``make_ec_model`` extends into an ecModel. This helper reads that file from the path set in the adapter -(``adapter.params.conv_gem``). The extension picks the reader: +(``adapter.params.conv_gem``). The extension selects the reader: ``.yml``/``.yaml`` via raven-toolbox's ``read_yaml_model`` (which, unlike cobra's reader, keeps RAVEN fields such as per-reaction ``eccodes``), ``.json`` via diff --git a/src/geckopy/utilities/pfba_enzymes.py b/src/geckopy/utilities/pfba_enzymes.py index 44adbd9..c7abb76 100644 --- a/src/geckopy/utilities/pfba_enzymes.py +++ b/src/geckopy/utilities/pfba_enzymes.py @@ -2,15 +2,15 @@ Standard FBA finds *some* flux distribution that maximises the objective. Parsimonious FBA (pFBA) goes one step further: among -all flux distributions that achieve the maximum, it picks the one +all flux distributions that achieve the maximum, it selects the one with the smallest total flux. This is the classical pFBA from -cobra-py — it doesn't know about enzymes. +cobra-py, which does not use enzyme information. ``pfba_enzymes`` is the enzyme-aware version: among all -biomass-optimal flux distributions, it picks the one that uses the +biomass-optimal flux distributions, it selects the one that uses the least enzyme mass (the smallest sum of ``usage_prot_*`` fluxes, weighted by the cost coefficients in the LP). For ecModels this is -usually what you want, because it picks the most parsimonious +usually what you want, because it selects the most parsimonious *proteome*, not just the most parsimonious *fluxes*. Ported from the legacy geckopy package described in Carrasco et al. @@ -134,7 +134,7 @@ def pfba_enzymes( ) m.objective.set_linear_coefficients({v: 1.0 for v in variables}) # Usage fluxes are already mass-scaled (the catalysed-reaction - # coupling carries MW/(kcat*3600) and prot_pool is in mg/gDW), so a + # coupling has MW/(kcat*3600) and prot_pool is in mg/gDW), so a # unit-weighted sum minimises total enzyme mass. if m.slim_optimize(error_value=None) is None: raise cobra.exceptions.OptimizationError( diff --git a/src/geckopy/utilities/save_ec_model.py b/src/geckopy/utilities/save_ec_model.py index 4561153..b460ff6 100644 --- a/src/geckopy/utilities/save_ec_model.py +++ b/src/geckopy/utilities/save_ec_model.py @@ -17,7 +17,7 @@ 3. **Empty-ecModel guard + provenance injection** — fast-fail when the caller forgot to run ``make_ec_model``; write a small ``metaData`` block (date, ecModel format, geckopy release, description) so saved - files carry provenance information. + files contain provenance information. The provenance/description metadata is written only into the saved YAML; the in-memory model's ``notes`` are mutated only transiently for diff --git a/tutorials/full_ecModel/protocol.py b/tutorials/full_ecModel/protocol.py index 6ee2645..d8308d2 100644 --- a/tutorials/full_ecModel/protocol.py +++ b/tutorials/full_ecModel/protocol.py @@ -86,7 +86,7 @@ # %% [markdown] # **STEP 12-13** Annotate with complex data. The Complex Portal -# JSON is shipped at `data/ComplexPortal.json`; the downloader +# JSON is included at `data/ComplexPortal.json`; the downloader # `geckopy.databases.get_complex_data` would refresh it. # %% @@ -150,7 +150,7 @@ # %% [markdown] # **STEP 23** Prepare DLKcat input file. The `data/DLKcat.tsv` -# shipped here already has predicted kcat values; uncomment to +# included here already has predicted kcat values; uncomment to # regenerate (this discards existing predictions). # # ```python @@ -163,7 +163,7 @@ # %% [markdown] # **STEP 24** Run DLKcat. (External tool; not invoked here. The -# shipped `data/DLKcat.tsv` already has output from a previous run.) +# included `data/DLKcat.tsv` already has output from a previous run.) # %% [markdown] # **STEP 25** Load DLKcat output from `data/DLKcat.tsv`, kept as diff --git a/tutorials/light_ecModel/protocol.py b/tutorials/light_ecModel/protocol.py index d07d589..4a7cdc0 100644 --- a/tutorials/light_ecModel/protocol.py +++ b/tutorials/light_ecModel/protocol.py @@ -42,7 +42,7 @@ # %% [markdown] # ## STAGE 1: Expansion from a starting GEM to a light ecModel # -# **STEP 8** Load the adapter. ecTestGEM ships in ``examples/`` so the +# **STEP 8** Load the adapter. ecTestGEM is included in ``examples/`` so the # tutorial is self-contained; no local ``model_adapter.toml`` is needed. # %% @@ -103,7 +103,7 @@ # pipeline). Here we set a couple of kcats by hand to demonstrate the # light dispatch. # -# In a light model the ``ec.rxns`` IDs carry a ``###_`` prefix, which is +# In a light model the ``ec.rxns`` IDs have a ``###_`` prefix, which is # part of the ID for ``set_kcat_for_reactions``, as in GECKO's # ``setKcatForReactions``: ``"001_R2"`` targets exactly that isozyme row, # so you can set different kcats per isozyme, and the bare cobra ID @@ -124,7 +124,7 @@ # **STEP 31** Push the kcats into the S-matrix. # # Light ``apply_kcat_constraints`` differs from the full version: for -# each cobra reaction it picks the *cheapest* isozyme (smallest +# each cobra reaction it selects the *cheapest* isozyme (smallest # ``MW_sum / kcat``) across all its ``###_`` rows, then writes a single # ``-MW_sum / (kcat * 3600)`` coefficient at ``S[prot_pool, rxn]``. No # per-enzyme constraints are written. From 1d79e419b7df220a1b84f751a4d47d781a0da8c8 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Fri, 6 Nov 2026 23:42:50 +0100 Subject: [PATCH 2/2] Remove internal planning documents that no longer describe geckopy ravenpy_split_report, porting_plan, raven_inventory, code_review, openkineticspredictor_plan and wildkcat_integration_plan are removed. brenda_refresh_plan stays. --- docs/internal/code_review.md | 30 -- docs/internal/openkineticspredictor_plan.md | 210 -------------- docs/internal/porting_plan.md | 56 ---- docs/internal/raven_inventory.md | 133 --------- docs/internal/ravenpy_split_report.md | 222 --------------- docs/internal/wildkcat_integration_plan.md | 261 ------------------ .../open_kinetics_predictor/__init__.py | 1 - 7 files changed, 913 deletions(-) delete mode 100644 docs/internal/code_review.md delete mode 100644 docs/internal/openkineticspredictor_plan.md delete mode 100644 docs/internal/porting_plan.md delete mode 100644 docs/internal/raven_inventory.md delete mode 100644 docs/internal/ravenpy_split_report.md delete mode 100644 docs/internal/wildkcat_integration_plan.md diff --git a/docs/internal/code_review.md b/docs/internal/code_review.md deleted file mode 100644 index 96ea80c..0000000 --- a/docs/internal/code_review.md +++ /dev/null @@ -1,30 +0,0 @@ -# geckopy code review — open items - -The kcat-aggregation thread is fully resolved. - -## kcat aggregation — resolved - -Decision: keep all included defaults at the MATLAB-GECKO values. The -adapter exposes three dedicated fields for project-wide overrides, and -the BRENDA snapshot includes both max and median per (ec, substrate, -organism) triple so flipping the runtime default does not require -regenerating the snapshot. - -| function | adapter param | included default | other allowed values | -|---|---|---|---| -| `fuzzy_kcat_matching` | `params.kcat_aggregate_brenda` | `"max"` | `"median"` | -| `apply_kcat_list` | `params.kcat_aggregate_candidates` | `"max"` | `"min"`, `"median"`, `"mean"` | -| `fill_kcats_from_isozymes` | `params.kcat_aggregate_isozymes` | `"mean"` | `"max"`, `"median"` | - -`fuzzy_kcat_matching` now selects the matching snapshot view -(`brenda.kcat_for(aggregate)` / `brenda.sa_for(aggregate)`) and applies -the same aggregation across the rows that survive the EC + organism + -substrate gates. So both layers — the per-triple snapshot collapse and -the per-EC runtime collapse — move together when the adapter setting -flips. - -See [docs/kcat_aggregation.md](kcat_aggregation.md) for the empirical -comparison; `parse.py`'s range collapse (`"0.1-2.5"` → upper bound) is -the one remaining max-leaning step in the pipeline. Small effect (only -fires on the subset of measurements reported as ranges) and not -wired — leave for a future ticket if it surfaces. diff --git a/docs/internal/openkineticspredictor_plan.md b/docs/internal/openkineticspredictor_plan.md deleted file mode 100644 index 0647b22..0000000 --- a/docs/internal/openkineticspredictor_plan.md +++ /dev/null @@ -1,210 +0,0 @@ -# OpenKineticsPredictor (OKP) direct-API integration - -MATLAB GECKO first (active). geckopy port deferred — see the end. - -## Goal - -Replace the manual OKP workflow (write `OKP.csv` -> upload by hand at -predictor.openkinetics.org -> download `job-*-output.csv` -> parse) -with two functions that talk to the OKP REST API directly: - -- `submitOpenKineticsPredictor` — build the input CSV and submit the - job; persist the returned job id. -- `fetchOpenKineticsPredictor` — check status; if finished, download + - save + parse into a kcatList. Has a stored-result mode and a polling - mode. - -The existing `writeOpenKineticsPredictorInput.m` and -`readOpenKineticsPredictorOutput.m` are **replaced**: their bodies move -in as local subfunctions of submit (build input) and fetch (parse -output) respectively. The stray `writeOpenKineticsPredictorInput.asv` -is deleted. - -## Verified API reference (live, 2026-05-20) - -Base: `https://predictor.openkinetics.org/api/v1` -Auth: `Authorization: Bearer ak_...`. Missing/invalid -> HTTP 401 -`{"error":"Authentication required. Include the header: ..."}`. -Async model: submit -> poll status -> download result. - -### API keys (anonymous, IP-bound) - -- `GET /api/api-key/` -> `{"hasKey":bool, "keySuffix":"e86abd"}` -- `POST /api/api-key/generate/` -> `{"key":"ak_...", "keySuffix":"..."}` - or `{"error":"An active API key already exists for your IP. Revoke it first."}` -- `POST /api/api-key/revoke/` -> `{"revoked":true}` - -No login/registration. Key shown only once. Out of scope for the two -functions — the user generates a key in the web UI and provides it. - -### POST /submit/ (multipart form) - -Form fields: `file=@OKP.csv`, `targets='["kcat"]'`, -`methods='{"kcat":"CataPro"}'`, `handleLongSequences=truncate`, -`includeSimilarityColumns=true`, `canonicalizeSubstrates=true`. - -Response: -```json -{"jobId":"BDUASPl","status":"Pending", - "statusUrl":"/api/v1/status/BDUASPl/", - "resultUrl":"/api/v1/result/BDUASPl/", - "quota":{"limit":20000,"used":1,"remaining":19999,"resetsInSeconds":18447}} -``` - -### GET /status/{jobId}/ - -```json -{"jobId":"BDUASPl","status":"Completed", - "submittedAt":"2026-05-20T18:52:32Z","elapsedSeconds":50, - "queueSeconds":0,"computeSeconds":50,"queuePosition":null, - "progress":{"moleculesTotal":1,"moleculesProcessed":1, - "predictionsTotal":1,"predictionsMade":1,"invalidRows":0, - "stages":[...]}, - "completedAt":"...","resultUrl":"/api/v1/result/BDUASPl/"} -``` -- `status` is **Title-case**: `Pending` / `Completed` (also expect - `Running` / `Failed`). Compare case-insensitively. -- Bad id -> HTTP 404 `{"error":"No job found with id 'NOPE123'."}`. - -### GET /result/{jobId}/ - -- Default: `Content-Type: text/csv`, - `Content-Disposition: attachment; filename="webkinpred--results.csv"`. - 7 columns: - `kcat (1/s), Source kcat, Extra Info kcat, Protein Sequence, Substrate, mean similarity to CataPro training data, max similarity to CataPro training data` -- `?format=json`: `{jobId, columns:[...], rowCount, data:[{...}]}` — - **but** the JSON contains bare `NaN` (`"Extra Info kcat": NaN`), - which MATLAB `jsondecode` rejects. **Use the CSV form.** - -The CSV's columns 1,2,4,5 (kcat, source, sequence, substrate) are -exactly what the existing parser reads; the 2 trailing similarity -columns are ignored by its fixed-index logic. - -### POST /validate/ (multipart, optional) - -`file=@OKP.csv`, `runSimilarity=false|true` (`true` runs a slow -MMseqs2 pass). Response: -```json -{"rowCount":1,"invalidSubstrates":[],"invalidProteins":[], - "lengthViolations":{"CataPro":0,...}, - "lengthLimits":{"CataPro":1000,"CatPred":2048,"DLKcat":null,...}, - "similarity":null} -``` - -### GET /methods/ (no auth) and /quota/ (auth) - -`/methods/` lists predictors per target with `id`, `supports`, -`maxSeqLen`, `requiredColumns`. `/quota/` -> -`{limit:20000, used, remaining, resetsInSeconds}` (resets midnight UTC; -failed/partial jobs are credited back). - -Live method roster + length limits: CataPro(1000), CatPred(2048), -DLKcat(no limit), EITLEM(1024), KinForm-H/L(1500), MMISA-KM(500), -OmniESI(1000), TurNup(1024), UniKP(1000). - -## MATLAB HTTP approach - -- **submit** (multipart file upload): `matlab.net.http.RequestMessage` - with `matlab.net.http.io.MultipartFormProvider` (file part via - `FileProvider`, the rest as string parts). `webwrite` cannot do - multipart form uploads, so this is required. Auth via - `matlab.net.http.HeaderField('Authorization',['Bearer ' apiKey])`. -- **status**: `webread` with `weboptions('HeaderFields',{'Authorization' ['Bearer ' apiKey]})`, - parse the returned struct. -- **result download**: `websave` (or `webread` returning text) with the - same auth header; write bytes to `data/OKP_output.csv`. - -## Function specs - -### submitOpenKineticsPredictor - -```matlab -jobId = submitOpenKineticsPredictor(model, ecRxns, modelAdapter, ... - method, apiKey, overwrite) -``` -1. Resolve params: `method` arg -> `params.okp.method` -> `'CataPro'`; - likewise `targets`, `handleLongSequences`, `includeSimilarityColumns`, - `canonicalizeSubstrates` from `params.okp.*` with built-in defaults. -2. Resolve API key: `apiKey` arg -> env `OKP_API_KEY` -> - `data/okpApiKey.txt`. Error (naming all three) if none. -3. Build `data/OKP.csv` via the in-lined input builder (former - `writeOpenKineticsPredictorInput`), honouring `overwrite`. -4. POST `/submit/` (multipart). On non-2xx, error with the `error` - field. -5. Write `data/OKP_job.txt` (plain text, one field per line): - ``` - jobId: BDUASPl - method: CataPro - targets: kcat - submittedAt: 2026-05-20T18:52:32Z - ``` -6. Return `jobId`; print the status URL and a "run - fetchOpenKineticsPredictor later" hint. - -### fetchOpenKineticsPredictor - -```matlab -[done, kcatList] = fetchOpenKineticsPredictor(model, useStored, ... - modelAdapter, jobId, wait, pollInterval) -``` -`useStored` is the **2nd** argument by request. - -- `useStored=true`: skip the API entirely. Parse `data/OKP_output.csv` - (former `readOpenKineticsPredictorOutput` logic) -> `kcatList`, - `done=true`. Error if the file is absent. -- `useStored=false` (default): - 1. `jobId` arg -> read from `data/OKP_job.txt`. - 2. Resolve API key (same as submit). - 3. GET `/status/{jobId}/`. - - `Completed` -> GET `/result/{jobId}/` (CSV), save to - `data/OKP_output.csv`, parse -> `kcatList`, `done=true`. - - `Pending`/`Running` -> if `wait=true`, poll every `pollInterval` - seconds until terminal/`timeout`; else print - `"Job not finished (status: Running). Try later, or check - https://predictor.openkinetics.org/"`, return `done=false`, - `kcatList=[]`. - - `Failed` -> error with the status body. - -Returned `kcatList` has the same shape the old reader produced -(`source`, `rxns`, `genes`, `substrates`, `kcats`, `kcatSource` with -`OKP-` labels), consumable by `selectKcatValue`. - -## ModelAdapter additions (adapterTemplate.m) - -A new optional `okp` block (non-secret config only; the key never goes -here): -```matlab -obj.params.okp.method = 'CataPro'; -obj.params.okp.targets = {'kcat'}; -obj.params.okp.handleLongSequences = 'truncate'; -obj.params.okp.includeSimilarityColumns = true; -obj.params.okp.canonicalizeSubstrates = true; -``` -Functions treat `params.okp` as optional and fall back to built-in -defaults, so existing adapters keep working untouched. - -## Files - -- New: `submitOpenKineticsPredictor.m`, `fetchOpenKineticsPredictor.m` -- Deleted: `writeOpenKineticsPredictorInput.m`, - `readOpenKineticsPredictorOutput.m`, `writeOpenKineticsPredictorInput.asv` -- Modified: `adapterTemplate.m` (+okp block) - -## Decisions locked - -- Default method: **CataPro**. -- Load-vs-fetch: **Option A** — one `fetch`, explicit `useStored` (2nd - arg), no silent fallback. Parsing always happens; fetch returns a - kcatList. -- API key: arg -> env `OKP_API_KEY` -> `data/okpApiKey.txt`, never the - adapter. -- Metadata file: plain text `data/OKP_job.txt`, one field per line. -- Result saved to `data/OKP_output.csv`. - -## geckopy port (later) - -Mirror as `gather_kcats/open_kinetics_predictor/` with `client.py` -(injectable requests.Session), `submit`/`fetch` entry points, the same -build-input / parse-output logic, key via env `OPENKINETICS_API_KEY` / -`.env`. Tests mock HTTP; opt-in live marker. Only needs `requests` -(already a dep). Not started. diff --git a/docs/internal/porting_plan.md b/docs/internal/porting_plan.md deleted file mode 100644 index 9c2c66d..0000000 --- a/docs/internal/porting_plan.md +++ /dev/null @@ -1,56 +0,0 @@ -# GECKO to geckopy porting plan - -How the MATLAB-to-Python port was scoped, what each piece maps to, -and which external libraries do what. - -## Target version - -Pinned to **GECKO v3.2.5** so the port has a stable reference and -the in-source `Ported from GECKO MATLAB: ...` comments resolve to -exact line numbers. - -## Scope - -Full port of all 82 MATLAB functions in `src/geckomat/` plus the -two reference tutorials (`full_ecModel`, `light_ecModel`). The -full ecModel build is complete; the light variant is documented -but not yet implemented — see [gecko_light_status.md](gecko_light_status.md). - -## Fidelity goal - -Model structure (which reactions, which metabolites, which -coefficients) and on-disk I/O must match the MATLAB output. Small -numerical differences are acceptable when they come from solver or -library choices (LP solvers do not always give the same alternate -optimum; floating-point math differs between Python and MATLAB). - -## Data model - -`EcModel` subclasses `cobra.Model` and has an extra `.ec` -attribute. `.ec` is an `EcData` dataclass that holds the kcat -array, enzyme list, MW array, the sparse reaction-enzyme coupling -matrix, and a few other vectors. See -[src/geckopy/ec_model/ec_data.py](../src/geckopy/ec_model/ec_data.py) -for the exact field set. - -## Dependency mapping - -| MATLAB-side dependency | Python-side replacement | -|---|---| -| **RAVEN** (model manipulation, FBA, I/O) | Mostly cobra-py; a handful of functions are re-implemented in geckopy itself. The remaining gap is catalogued in [raven_inventory.md](raven_inventory.md) as the suggested scope for a future ravenpy package. | -| **COBRA Toolbox** | cobra-py | -| **Gurobi / GLPK / CPLEX solvers** | The same solvers, accessed via cobra-py and its optlang layer | -| **BRENDA / UniProt / KEGG HTTP APIs** | `requests` | -| **MATLAB `.mat` reference files** | `scipy.io.loadmat` | - -## Function inventory - -Per-subsystem porting status. (The table below is a placeholder -from early planning; it has not been kept up to date as functions -landed. The authoritative status is the existence of the Python -module and its passing tests — see the source tree under -`src/geckopy/` and `tests/`.) - -| Subsystem | MATLAB function | Python target | Deps (RAVEN / other) | Status | -|-----------|-----------------|---------------|----------------------|--------| -| model_adapter | ... | ... | ... | todo | diff --git a/docs/internal/raven_inventory.md b/docs/internal/raven_inventory.md deleted file mode 100644 index cf3e4e8..0000000 --- a/docs/internal/raven_inventory.md +++ /dev/null @@ -1,133 +0,0 @@ -# RAVEN function inventory - -GECKO MATLAB is built on top of -[RAVEN](https://github.com/CompMetEng/RAVEN), a MATLAB toolkit -for genome-scale metabolic modelling. RAVEN does all the -low-level work: parsing SBML files, editing reactions and -metabolites, solving FBAs, manipulating gene-protein-reaction -rules, and so on. - -As the geckopy port progressed, every RAVEN call in the MATLAB -source had to land somewhere on the Python side. There were three -possibilities: - -1. **cobra-py already does it.** Most cases — cobra-py is RAVEN's - Python-side equivalent and covers the bread-and-butter - operations. -2. **geckopy implements it internally.** A few RAVEN functions - were specific enough to GECKO's needs that re-implementing them - in geckopy itself made more sense than pulling in a separate - dependency. -3. **No Python equivalent exists yet.** These are the - functions worth eventually porting into a separate **ravenpy** - package. cobra-py either doesn't do them at all or does them - differently enough that callers can't drop one in for the - other. - -This document catalogues all three groups, plus a fourth (RAVEN -helpers that don't translate to Python at all — MATLAB-specific -glue like file dialogs). - -The list is based on a scan of `src/geckomat/**/*.m` in GECKO -MATLAB and the existing pipeline modules in -`src/geckopy/ec_model/pipeline/`. It only covers RAVEN functions -that GECKO actually uses; the full RAVEN library is much larger. - -## Why this document is useful - -Two reasons: - -- **For porting more MATLAB code.** If you're translating a piece - of MATLAB GECKO (or any RAVEN-based code) to Python, look here - first to find the cobra-py / geckopy substitute. -- **For scoping ravenpy.** Group 3 is the proposed v0.1 of a - ravenpy package — a Python port of the bits of RAVEN that - cobra-py doesn't cover. - -## 1. Replaced by cobra-py directly - -These are the easy ones. RAVEN's API was MATLAB-flavoured (verb + -model + string id); cobra-py's is object-oriented (model.. -get_by_id(...).), but they do the same thing. - -| RAVEN | cobra-py equivalent | -|---|---| -| `setParam(model, 'lb'\|'ub'\|'eq', rxn, val)` | `model.reactions.get_by_id(rxn).lower_bound = val` (or `.upper_bound`; for `obj`, `model.objective = rxn`) | -| `solveLP(model)` | `model.optimize()` | -| `importModel(filename)` | `cobra.io.read_sbml_model(filename)` | -| `exportModel(model, filename)` | `cobra.io.write_sbml_model(model, filename)` | -| `getIndexes(model, ids, 'rxns'\|'mets'\|'genes')` | `model.reactions.get_by_id(id)` (cobra-py is keyed by id, no numeric indices needed) | -| `addRxns(model, rxnsToAdd)` | `model.add_reactions([cobra.Reaction(...)])` | -| `addMets(model, metsToAdd)` | `model.add_metabolites([cobra.Metabolite(...)])` | -| `removeReactions(model, rxnList)` | `model.remove_reactions(rxnList)` | -| `removeMets(model, metList)` | `model.remove_metabolites(metList)` | -| `removeGenes(model, geneList)` | `model.genes.remove(model.genes.get_by_id(g))` per id | -| `addGenes(model, geneList)` | not needed — cobra-py adds genes automatically when a reaction's GPR mentions them | - -## 2. Replaced by geckopy internals - -A few RAVEN functions are tied closely enough to GECKO's needs -(isozyme expansion, reversibility splitting) that geckopy -re-implements them in the build pipeline rather than depending -on an external package. - -| RAVEN | geckopy module | -|---|---| -| `expandModel` | `src/geckopy/ec_model/pipeline/expand.py::expand_model`. Uses cobra-py's GPR abstract syntax tree instead of RAVEN's string-manipulation approach (more robust against unusual GPR formatting). | -| `convertToIrrev` | `src/geckopy/ec_model/pipeline/preprocess.py::convert_to_irreversible`. Splits each reversible reaction into a forward and a reverse half, the reverse getting a `_REV` suffix. | -| pseudoreaction handling / backward-only inversion / bound validation | Also in `preprocess.py` — stages 1-4 of the build pipeline. | -| `readYAMLmodel`, `writeYAMLmodel` (cobra portion only) | `src/geckopy/utilities/load_ec_model.py`, `save_ec_model.py`. But geckopy uses its own canonical YAML schema — see [yaml_format.md](yaml_format.md) for the migration story. | - -## 3. RAVEN-only — ravenpy candidates - -These have no drop-in cobra-py equivalent. Each one provides a -useful, biochemically-informed structural or optimization utility -that's orthogonal to cobra-py's core FBA engine. They're the -suggested scope for a future **ravenpy** package. - -| Function | What it does | Why cobra-py can't do it natively | -|---|---|---| -| `fillGaps` | Repairs an infeasible model by adding reactions from a reference model (gap-filling) | cobra-py has `cobra.flux_analysis.gapfill` but it's a different algorithm with a different signature; calling it as a `fillGaps` drop-in doesn't work | -| `simplifyModel` | Removes dead-end metabolites and orphan reactions by iteratively propagating constraints | cobra-py has no built-in equivalent; everyone re-rolls it | -| `contractModel` | Reduces a model by removing reactions that flux analysis shows are redundant | not in cobra-py | -| `haveFlux(model, rxnList)` | Asks per reaction "could this carry non-zero flux?" with numerical-tolerance handling | cobra-py has `find_blocked_reactions` but the tolerance semantics differ | -| `getAllowedBounds(model)` | Tightens each reaction's bounds via FVA under the current constraints | cobra-py has `flux_variability_analysis` but it returns a DataFrame, not a tightened model | -| `gprConsolidate(model, grRules)` | Simplifies GPRs using Boolean algebra (`(A or A) and B` -> `A and B`) | cobra-py's `GPR` class parses and evaluates, but doesn't simplify | -| `standardizeGrRules(model)` | Normalises GPR syntax (consistent parenthesisation, whitespace) | cobra-py's parser is liberal in what it accepts, but doesn't normalise on output | -| `changeGeneAssociation(model, rxn, newGpr)` | Replaces a reaction's GPR and keeps the gene list consistent | `rxn.gene_reaction_rule = ...` works in cobra-py, but doesn't garbage-collect orphan genes | -| `getGenesFromGrRules(grRules)` | Extracts gene IDs from a GPR rule string | cobra-py's `GPR.genes` works on the parsed AST, not raw strings | -| `mergeCompartments` | Consolidates two compartments into one | not in cobra-py | -| `copyToCompartments(model, mets, src, tgt)` | Replicates metabolites and their reactions into another compartment | not in cobra-py | -| `removeBadRxns` | Flags reactions with structural issues (impossible stoichiometry, missing genes, ...) for review | not in cobra-py | -| `readYAMLmodel`, `writeYAMLmodel` (RAVEN dialect) | Reads / writes the RAVEN-flavoured YAML, which differs schematically from cobra-py's | needed only for reading legacy `ecYeastGEM.yml`-style files; geckopy uses a different YAML format (see [yaml_format.md](yaml_format.md)) | - -## 4. MATLAB-specific helpers — not needed in Python - -RAVEN bits that don't translate at all, either because they're -MATLAB-specific (struct manipulation, file dialogs) or because -their job is already handled inside cobra-py / optlang. - -| Function | Why it's not ported | -|---|---| -| `solveLP(..., minFlux, hsSol)` hot-start | MATLAB-specific solver state; optlang / cobra-py do warm-start internally | -| `setRavenSolver(...)` | MATLAB CPLEX / Gurobi setup; cobra-py uses `model.solver = "gurobi"` | -| `importExcelModel`, `checkFileExistence`, dialog UI prompts | File I/O glue, not algorithmic | -| `convertCharArray`, `dispEM` | MATLAB cell-array and struct utilities | - -## Suggested ravenpy scope - -If someone starts a ravenpy package, the highest-value v0.1 -covers the GPR utilities (`gprConsolidate`, -`standardizeGrRules`, `getGenesFromGrRules`) plus `fillGaps` and -`simplifyModel`. These are the most-called RAVEN gaps in MATLAB -GECKO and have the clearest "RAVEN does this, cobra-py doesn't" -signature. - -The compartment-manipulation functions -(`mergeCompartments`, `copyToCompartments`) and `removeBadRxns` -are next on the list but get called in fewer places. - -The YAML reader/writer is its own concern. geckopy already uses -a canonical cobra-py-compatible schema (see -[yaml_format.md](yaml_format.md)), so the RAVEN-dialect reader is -really only useful for backward compatibility with legacy files. diff --git a/docs/internal/ravenpy_split_report.md b/docs/internal/ravenpy_split_report.md deleted file mode 100644 index c29de86..0000000 --- a/docs/internal/ravenpy_split_report.md +++ /dev/null @@ -1,222 +0,0 @@ -# What belongs in `ravenpy` vs `geckopy` - -## Premise - -Proposed three-layer stack: - -``` -cobrapy — core GEM object model + FBA/FVA/IO (no GECKO, no RAVEN) - ▲ -ravenpy — generic GEM reconstruction/annotation/analysis utilities - useful for ANY model, including ecGEMs (depends on cobrapy) - ▲ -geckopy — everything specific to enzyme-constrained GEMs (depends on ravenpy) -``` - -The test applied to every function below: **"Would a researcher working on a plain -GEM (no enzyme constraints) ever want this?"** If yes → `ravenpy`. If it only makes -sense once a model contains kcats / a protein pool / enzyme usage → `geckopy`. - -A second, sharper test for the borderline cases: **does the function actually read or -write the `ec` substructure, or does it just operate on a `cobra.Model`?** Many -functions currently take an `EcModel` purely out of convenience but never touch -`.ec` — those are generic and the `EcModel` type hint is the only thing tying them -to geckopy. - -Note: this is a *destination* analysis, not a "rip it out now" plan. Several items -are generic in intent but currently coupled to `model.ec` or to the ecModel adapter; -for those the recommendation is "the kernel belongs in ravenpy, with a thin ec-aware -wrapper left in geckopy." Those are called out explicitly. - ---- - -## Tier A — move as-is (clearly generic, little/no coupling) - -These operate on a `cobra.Model` (or raw data) and have obvious value for non-ec GEMs. -Several already have docstrings naming their RAVEN MATLAB origin. - -### Structural model surgery — `ec_model/pipeline/preprocess.py`, `expand.py` -| function | what it does | why generic | -|---|---|---| -| `convert_to_irreversible` | split reversible non-exchange reactions into fwd/`_REV` | docstring already says *"Wraps RAVEN: convertToIrrev.m. RAVENpy candidate."* Irreversible models are a generic preprocessing form (sampling, MILP, FSEOF). | -| `invert_backwards_only_reactions` | rewrite `lb<0, ub==0` reactions in canonical forward form | pure bound/stoichiometry canonicalization; nothing enzyme-related | -| `remove_pseudoreaction_gprs` | clear GPRs on biomass/pseudo reactions | GPR hygiene any reconstruction wants; takes only a model + a TSV path | -| `expand_model` + `_gpr_to_dnf` / `_node_to_dnf` | split isozyme (OR-GPR) reactions, one per clause | docstring: *"Equivalent to RAVEN: expandModel.m"*. The GPR→DNF helper is a generally useful GPR utility (cobrapy has no DNF expander). | - -These four touch only `cobra` objects — no `.ec` access at all. They are the single -strongest, lowest-risk batch to lift into `ravenpy`. - -### Sequence / annotation databases — `databases/` -| module | what it does | why generic | -|---|---|---| -| `uniprot_download.py` + `uniprot_loader.py` (`UniprotDB`) | fetch & parse UniProt entries (sequence, MW, gene names, EC) by taxon/proteome | genome annotation; RAVEN's `getUniprot`. Useful for any reconstruction. | -| `kegg_download.py` + `kegg_loader.py` | fetch & parse KEGG gene entries (sequence, EC, pathway, MW) | RAVEN's `getKEGGModelForOrganism`/KEGG helpers. Generic. | -| `phyl_dist.py` (`PhylDist`) | KEGG phylogenetic distance matrix + genus lookup | RAVEN's `getPhylDist`. A generic comparative-genomics resource. | -| `mw.py` (`calculate_mw`) | molecular weight from an amino-acid sequence | a generic protein property; nothing ec about it | -| `pubchem.py` (`find_met_smiles`) | look up metabolite SMILES by name | generic metabolite annotation (cheminformatics, met matching, thermo) | -| `flux_data.py` (`FluxData`, `load_flux_data`) | parse experimental exchange/flux measurements with `(rxn)` headers | generic experimental-data ingestion | -| `pax_db_loader.py`, `prot_data_loader.py` (`ProtData`) | load & filter proteomics abundances | generic omics integration (E-Flux, GIMME, etc. all consume proteomics) | - -Their result dataclasses (`UniprotDB`, `PhylDist`, `ProtData`, `FluxData`) move with them. - -### Generic constraint application — `limit_proteins/constrain_flux_data.py` -`apply_flux_data_constraints` (aliased `constrain_flux_data`) applies measured exchange -and growth fluxes as bounds. **Despite living in `limit_proteins`, it has nothing to do -with proteins** — it constrains any GEM to experimental flux data. Clear `ravenpy` -function. (It currently reaches into the adapter for `c_source`/`bio_rxn`; see the -adapter note below.) - -### Model loading — `utilities/load_conventional_gem.py` -A thin reader that dispatches YAML vs SBML. Generic ("import a GEM"); RAVEN's -`importModel`. The only coupling is that it takes the adapter to find the path — -trivial to also expose a plain `load_gem(path)`. (Also a good place to add the missing -`.json`/`.mat` dispatch noted in the code review.) - ---- - -## Tier B — generic kernel, currently ec-coupled (move the kernel, keep a thin wrapper) - -These accomplish something generic but are wired to `model.ec` today. The -recommendation is to factor the generic operation into `ravenpy` operating on a -`cobra.Model`, and leave a small geckopy wrapper that also maintains the `ec` arrays. - -### EC-code assignment — `get_enzyme_data/` -EC numbers are standard metabolic annotation, valuable for **any** GEM (pathway -mapping, model comparison, gap-filling), not just ecGEMs. The logic here is generic; -only the *storage target* (`model.ec.eccodes`) is ec-specific. - -| module | generic core | ec coupling to strip | -|---|---|---| -| `ec_from_gem.py` (`fill_eccodes_from_gem`) | read & validate `ec-code` annotations off reactions | writes to `model.ec.eccodes` | -| `ec_from_database.py` (`fill_eccodes_from_database`) | look up EC numbers from UniProt/KEGG by gene | writes to `model.ec`; uses adapter | -| `find_ec_in_db.py` | EC-code matching incl. wildcard subsumption (`1.2.-.-`) | the wildcard/subsumption logic is fully generic; the MW return is enzyme-flavoured | -| `copy_ec_to_gem.py` | write EC strings back into reaction annotations | reads `model.ec.eccodes` | - -Suggested shape: ravenpy gets `annotate_ec_from_database(model, ...)` writing to -`reaction.annotation['ec-code']`; geckopy's version additionally mirrors into -`ec.eccodes`. The EC-wildcard matcher in `find_ec_in_db.py` is independently useful and -should be a public ravenpy helper. - -### Strain-design scanning — `utilities/ec_fseof.py` -FSEOF (Flux Scanning with Enforced Objective Function, Choi et al. 2010) is a **generic -strain-design method that predates ecModels**. The flux-scan-and-rank core works on any -GEM. The geckopy version adds enzyme/usage-reaction targets and the `usage_prot_*` -exclusion. Split: a `ravenpy.fseof(model, target, ...)` returning reaction/gene targets, -and a geckopy `ec_fseof` that layers enzyme targets on top. - -### Flux aggregation over split reactions — `utilities/map_rxns_to_conv.py` -Maps `_EXP_`/`_REV` split reactions back to their conventional parent and sums fluxes. -This is the natural companion to `convert_to_irreversible` + `expand_model` (both Tier -A) — **any** irreversible/expanded RAVEN-style model needs to fold fluxes back. The -flux-folding logic is generic; only the `enz_usage_2d` extraction is ec-specific. Move -the generic reverse-mapping to ravenpy. - -### Model subsetting — `utilities/get_subset_ec_model.py` -Extracting a sub-model by genes/reactions and pruning orphans is generic (RAVEN's -`getModelSubset`/`removeReactions`/`removeGenes`). Here it additionally trims the `ec` -arrays. Generic subsetting → ravenpy; the ec-array trimming stays as a geckopy override. - -### Reaction addition with isozyme expansion — `utilities/add_new_rxns_to_ec.py` -Adding reactions and expanding their GPRs is generic; the `ec` array bookkeeping is -specific. The expand-on-insert behavior reuses the Tier-A `expand_model` logic. - -### Complex stoichiometry data — `databases/complex_portal_download.py` + `complex_portal_loader.py` -Downloading/parsing Complex Portal protein-complex stoichiometry is generic -protein-annotation data (interactome studies, complex-aware analyses). The *consumer* -(`apply_complex_data`, which writes subunit counts into `rxn_enz_mat`) is ec-specific. -Move the acquisition/parsing (`ComplexPortalEntry`, `get_complex_data`, -`load_complex_portal_json`) to ravenpy; keep `apply_complex_data` in geckopy. - ---- - -## Tier C — the adapter / project scaffolding (split the schema) - -`adapter/` is generic *project* infrastructure (a folder convention, a TOML config, a -template generator, organism metadata) carrying an ec-specific *payload*. - -- **Generic → ravenpy**: the `ModelAdapter` machinery (`from_folder`, `resolve.py`, - `template.py`), and the generic fields of `ModelParameters`: `path`, `conv_gem`, - `org_name`, `c_source`, `bio_rxn`, and the data-source sub-schemas `KeggParams`, - `UniprotParams`, `ComplexParams`. -- **ec-specific → geckopy**: `sigma`, `p_tot`, `f`, `gr_exp`, `enzyme_comp`, and the - `EvotuneParams` / `OkpParams` (kcat tuning / prediction) sub-schemas. - -Cleanest design: a `ravenpy` base adapter/params, subclassed in geckopy to add the -enzyme parameters. This also fixes a coupling smell flagged in the code review — many -generic functions currently demand a *full* ecModel adapter (`resolve_adapter`) when -they only need `path`, `org_name`, or one reaction ID. - ---- - -## Stays in geckopy (genuinely enzyme-constrained) - -For completeness, these are correctly ec-specific and should **not** move: - -- **Core ec object model**: `ec_model/ec_model.py` (`EcModel`), `ec_data.py` (`EcData`), - `enzyme.py` (`Enzyme`/`EnzymeView`), `make_ec_model.py`, `constants.py`. -- **Building the enzyme layer**: `pipeline/populate_ec.py`, `apply_kcat.py`, - `protein_pool.py`, `apply_complex_data.py` (the *apply*, not the download), - `apply_custom_kcats.py`, `fill_kcats.py`, `set_kcat.py`, `query.py`. -- **kcat acquisition**: all of `gather_kcats/` (DLKcat, fuzzy BRENDA matching, OKP, - merge/select), plus `databases/brenda*` and `databases/dlkcat_ignore_lists.py`. -- **Protein-budget constraints**: `limit_proteins/` except `constrain_flux_data.py` — - i.e. `calculate_f_factor`, `constrain_enz_concs`, `fill_enz_concs`, - `flexibilize_enz_concs`, `get_conc_control_coeffs`, `relax_proteomics_greedy`. -- **kcat tuning**: all of `kcat_sensitivity_analysis/`. -- **Enzyme analytics**: `utilities/enzyme_usage.py`, `report_enzyme_usage.py`, - `bottlenecks.py`. -- **ec model I/O**: `io/sbml.py`, `utilities/save_ec_model.py`, `load_ec_model.py` (the - GECKO SBML/YAML extension format). -- **ec FVA**: `utilities/ec_fva.py` — though see the note below; the parallel-FVA - harness inside it is generic even if the conventional-reaction grouping is not. - -### Two near-misses worth noting -- **`databases/brenda*`** is the one "database" cluster that stays: BRENDA is queried - here purely for turnover numbers (kcat / specific activity), which is intrinsically - enzyme-kinetic. If ravenpy ever wanted generic enzyme-kinetic annotation (Km, etc.), - the BRENDA *download/parse* infrastructure could be promoted, but as used today it is - ec-specific. -- **`databases/dlkcat_ignore_lists.py`** is DLKcat-specific, but the *concept* of a - "currency metabolite" blocklist is generic (network-distance, transport detection in - FSEOF all need it). Consider a generic currency-metabolite list in ravenpy that the - DLKcat list extends. - ---- - -## Cross-cutting consequences of the split - -1. **Dependency direction & types.** ravenpy functions must type-hint `cobra.Model`, - not `EcModel`. Today many generic functions hint `EcModel` only for convenience — - that hint is what would otherwise force a circular `ravenpy → geckopy` dependency. - Audit every Tier-A/B function and widen the type to `cobra.Model`. - -2. **Drop the `ec_`/`*_ec_*` naming in ravenpy.** `ec_fseof`→`fseof`, - `load_conventional_gem`→`load_gem`, `fill_eccodes_from_gem`→`annotate_ec_from_gem`, - `get_subset_ec_model`→`get_model_subset`, `add_new_rxns_to_ec`→`add_reactions`. The - geckopy wrappers keep the ec-flavoured names. - -3. **The adapter must be decoupled** (Tier C) before the database/flux functions can - move cleanly, since most of them currently pull organism/path/taxon parameters out - of the ecModel adapter. - -4. **`model.ec` writes are the dividing line.** A useful mechanical first pass: grep - each candidate for `.ec` access. Zero `.ec` references ⇒ Tier A (move outright). - `.ec` only on output ⇒ Tier B (factor the kernel, keep an ec-writing wrapper). - ---- - -## Suggested migration order - -1. **Tier A structural surgery** (`preprocess.py`, `expand.py`) — zero `.ec` coupling, - already RAVEN-annotated, immediately usable. -2. **Tier A databases + `flux_data`/proteomics + `apply_flux_data_constraints`** — but - first do the **adapter split** (Tier C) so they don't drag the ec adapter along. -3. **Tier B EC-code assignment and FSEOF** — refactor to operate on `cobra.Model`, - leave ec wrappers in geckopy. -4. **Tier B subsetting / reaction-add / map-to-conv / complex download** — last, since - they interleave generic and ec bookkeeping most tightly. - -The biggest payoff is conceptual: after the split, `geckopy` shrinks to *only* the -enzyme-constraint mechanics (kcats, protein pool, enzyme usage, kcat tuning), and a -large, independently useful reconstruction/annotation/analysis toolkit (`ravenpy`) -becomes available to the whole cobrapy ecosystem. diff --git a/docs/internal/wildkcat_integration_plan.md b/docs/internal/wildkcat_integration_plan.md deleted file mode 100644 index 7c6f13a..0000000 --- a/docs/internal/wildkcat_integration_plan.md +++ /dev/null @@ -1,261 +0,0 @@ -# WILDkCAT integration plan (Strategy B — side-by-side, no deprecation) - -## Goal - -Add [WILDkCAT](https://github.com/sysbiolux/WILDkCAT) (PyPI: `wildkcat`) -as an **alternative** kcat-gathering pathway in geckopy. Keep the -existing fuzzy BRENDA matcher and ML wrappers -([gather_kcats/fuzzy_kcat_matching/](../../src/geckopy/gather_kcats/fuzzy_kcat_matching/), -[run_dlkcat.py](../../src/geckopy/gather_kcats/run_dlkcat.py), -[read_dlkcat_output.py](../../src/geckopy/gather_kcats/read_dlkcat_output.py)) -fully functional. Both pathways must terminate in the same -downstream API: a dataframe that -[apply_kcat_list](../../src/geckopy/gather_kcats/select_kcat_value.py) -accepts. - -Users opt into WILDkCAT explicitly. A later PR — outside this plan — -can promote WILDkCAT to default and deprecate the in-tree path once -benchmarks back the switch. - -## Why Strategy B - -- Lowest risk: zero behaviour change for existing users. -- WILDkCAT is a heavy optional dep (BRENDA login, Entrez email, - CataPro weights) and we should not force it on the default install. -- Lets us run a head-to-head benchmark on the Crabtree tutorial - before retiring ~1.5k LOC of fuzzy + ML scaffolding. - -## Integration boundary - -Both pipelines must produce a `pandas.DataFrame` with at least -these columns (current contract of -[fuzzy_kcat_matching](../../src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py#L67-L78) -and consumed by -[apply_kcat_list](../../src/geckopy/gather_kcats/select_kcat_value.py)): - -| column | type | meaning | -|----------|-------|--------------------------------------------------------| -| rxn_id | str | matches `model.ec.rxns` | -| source | str | `"brenda"` / `"dlkcat"` / **new:** `"wildkcat"` | -| kcat | float | s^-1; `0.0` means "no match" | - -Optional columns already used downstream (origin/wildcard) stay -opt-in. Anything WILDkCAT-specific (penalty_score, db_source, -SMILES) is exposed as **extra columns** on the same dataframe so -the user can inspect them; `apply_kcat_list` ignores them. - -## Files to add - -``` -src/geckopy/gather_kcats/wildkcat/ - __init__.py # public surface: gather_kcats_wildkcat - config.py # .env / BRENDA / Entrez resolution + validation - runner.py # build WILDkCAT input from model, invoke pipeline - adapter.py # WILDkCAT output -> geckopy kcat dataframe -tests/ - test_wildkcat_adapter.py # pure mapping tests, no network - test_wildkcat_runner.py # runner with mocked wildkcat module - fixtures/ - wildkcat_output_sample.tsv -``` - -## Files to modify - -- `pyproject.toml` — new optional-deps group: - ```toml - [project.optional-dependencies] - wildkcat = ["wildkcat>=", "python-dotenv>=1"] - ``` - Select `` from the first PyPI release that includes the - programmatic entry point we depend on (see step 1 below). -- [src/geckopy/gather_kcats/__init__.py](../../src/geckopy/gather_kcats/__init__.py) - — re-export `gather_kcats_wildkcat` **inside a lazy import** - so users without the optional dep are not penalised on - `import geckopy`. -- [src/geckopy/__init__.py](../../src/geckopy/__init__.py) — do **not** - promote to top-level for now; users call - `from geckopy.gather_kcats.wildkcat import gather_kcats_wildkcat`. -- [docs/internal/porting_plan.md](porting_plan.md) — add a note that WILDkCAT - is now a parallel pathway. -- [README.md](../../README.md) — one sentence under "Installation" - pointing to the new optional install + the doc page. - -## New doc - -- `docs/guides/wildkcat.md` — user-facing how-to: install, - `.env` setup, minimal call, output schema, when to prefer it - over the in-tree pipeline. - -## Implementation steps - -### 1. Pin the WILDkCAT API surface -- `pip install wildkcat` in a scratch venv. -- Identify the **programmatic entry** (function, not just CLI). - WILDkCAT's README documents a CLI; check - `wildkcat.__init__`, `wildkcat/main.py` or equivalent for a - Python callable. If only a CLI exists, fall back to driving - it via `subprocess` and parsing the TSV — this is acceptable - for Strategy B but should be flagged. -- Record the **input schema** WILDkCAT expects: at minimum - `(rxn_id, ec_number, substrate_name | substrate_inchi | - substrate_smiles, organism)`. -- Record the **output schema** WILDkCAT emits and which columns - contain kcat (s^-1 vs min^-1; confirm and unit-convert in - the adapter if needed). - -### 2. `config.py` -- Resolve credentials in this order: - 1. arguments passed to `gather_kcats_wildkcat(...)` - 2. environment variables (`BRENDA_EMAIL`, `BRENDA_PASSWORD`, - `ENTREZ_EMAIL`, plus any CataPro paths) - 3. `.env` in the working directory (via `python-dotenv`, - loaded only on demand) -- Raise `ValueError` with an actionable message — naming the - missing variable — if nothing is found. -- **Do not** fall through silently to an anonymous BRENDA call; - WILDkCAT will fail and produce a confusing stack trace. - -### 3. `runner.py` -- `gather_kcats_wildkcat(model, *, organism=None, config=None, - workdir=None, **wildkcat_kwargs) -> pd.DataFrame` -- Mirror the entry-point shape of - [fuzzy_kcat_matching](../../src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py#L78-L99): - same first positional arg, same return type, no surprises. -- Build the WILDkCAT input table by walking `model.ec.rxns`: - - `rxn_id` <- `model.ec.rxns[i]` (preserve the `ec_` prefix - for light models so the adapter can read the id back) - - `ec_number` <- `model.ec.eccodes[i]` - - `substrate` <- substrate name(s) from the EC reaction's - metabolites; reuse the substrate-extraction logic that - used to live in `_extract_substrates` and now lives inline - in [fuzzy_kcat_matching.__init__](../../src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py) - (refactor: lift it back into - `gather_kcats/_substrates.py` so both pathways share it - — single source of truth, ~30 LOC) - - `organism` <- `organism` arg, falling back to - `model.adapter.params.org_name` -- Reactions with empty EC or no substrates contribute a row - with `kcat=0.0` and `source="wildkcat"` — matches the - fuzzy-matcher convention. -- Persist the WILDkCAT input TSV to `workdir` (default a - temp dir, but accept user path for reproducibility), invoke - WILDkCAT, read back the output TSV. -- Wrap WILDkCAT failures in a `RuntimeError` that surfaces the - log path; do not swallow the original exception. - -### 4. `adapter.py` -- `wildkcat_tsv_to_kcat_df(path_or_df) -> pd.DataFrame` -- Pure function, no I/O beyond reading the supplied path. -- Column-by-column mapping with a documented map at the top of - the module. Unit-convert if WILDkCAT emits min^-1. -- Drop unknown rows (rxn_id not in the original input) with a - `logger.warning` listing them — symmetrical to how - [apply_kcat_constraints](../../src/geckopy/ec_model/pipeline/apply_kcat.py) - warns on unmatched rxns. -- Preserve WILDkCAT-specific columns (`penalty_score`, - `db_source`, etc.) so the caller can inspect them. - -### 5. Re-export with a lazy guard -In [gather_kcats/__init__.py](../../src/geckopy/gather_kcats/__init__.py): - -```python -def __getattr__(name): - if name == "gather_kcats_wildkcat": - try: - from .wildkcat import gather_kcats_wildkcat - except ImportError as exc: - raise ImportError( - "WILDkCAT support requires the optional dependency: " - "pip install 'geckopy[wildkcat]'" - ) from exc - return gather_kcats_wildkcat - raise AttributeError(name) -``` - -`__all__` stays as-is — we do not advertise the symbol in -tab-completion until the optional dep is present. - -### 6. Tests - -- `test_wildkcat_adapter.py` — feed a 5-row fixture TSV into - `wildkcat_tsv_to_kcat_df`, assert column names, types, unit - conversion, source label, warning on unknown rxn_id. -- `test_wildkcat_runner.py` — monkeypatch the WILDkCAT entry - point to a stub that writes a known TSV; assert the runner: - (a) builds the correct input table, (b) returns the adapted - df, (c) raises `ValueError` on missing credentials, - (d) returns `kcat=0.0, source="wildkcat"` rows for ec - reactions with no substrate. -- Mark **no** network test as required in CI. Add an opt-in - `@pytest.mark.wildkcat_live` marker, off by default, gated - on real credentials in `.env`. Document in - [README.md](../../README.md) how to enable it. -- Target test count after this PR: 172 (current) + ~12 new. - -### 7. Benchmark helper (optional, can be included in the same PR) - -`src/geckopy/gather_kcats/benchmark.py`: - -```python -def benchmark_kcat_pipelines(model, organism, *, brenda_path, - wildkcat_config=None) -> pd.DataFrame -``` - -Returns one row per ec reaction with columns -`rxn_id, fuzzy_kcat, fuzzy_source, wildkcat_kcat, -wildkcat_source, agree_within_2x`. Intended to be run once -on the [full_ecModel tutorial](../../tutorials/) and pasted into -the eventual default-switch PR. - -This helper is the data that will justify deprecating the -fuzzy + ML path in a future PR. Without it, the deprecation -decision has nothing to stand on. - -### 8. Docs - -`docs/guides/wildkcat.md`: -- One-paragraph context (what WILDkCAT is, why offer it) -- Install: `pip install 'geckopy[wildkcat]'` -- `.env` template (no real credentials in the file) -- Minimal example: model load -> `gather_kcats_wildkcat` -> - `apply_kcat_list` -> `apply_kcat_constraints` -> solve -- Output-column reference -- Caveats: first run downloads CataPro weights, requires - Python >= 3.11 (matches geckopy) - -## Out of scope (explicit non-goals) - -- Removing or deprecating [fuzzy_kcat_matching](../../src/geckopy/gather_kcats/fuzzy_kcat_matching/) - or DLKcat wrappers. Tracked for a follow-up after benchmarks. -- Wrapping CataPro standalone (Strategy D). The full WILDkCAT - pipeline already runs CataPro. -- Promoting `gather_kcats_wildkcat` to top-level `geckopy.*`. -- Changing the MATLAB GECKO side. - -## Verification - -- `cd /mnt/c/Work/GitHub/geckopy && source .venv/bin/activate && - pytest` — expect baseline + ~12 new tests, all passing on a - machine **without** the `wildkcat` extra installed - (the lazy `__getattr__` keeps it green). -- `pip install '.[wildkcat]' && pytest` — same, plus the - WILDkCAT-specific tests are now collected and pass with the - mocked entry point. -- Manual: run `gather_kcats_wildkcat` on the Crabtree tutorial - ecYeastGEM, save the output, diff against the in-tree - pipeline using the new benchmark helper, attach the table - to the PR description. - -## Rollout - -1. Land this PR. No user-visible default change. -2. Run the benchmark on ecYeastGEM + at least one other - organism (E. coli or human) — attach to the PR or to a - follow-up issue. -3. If WILDkCAT coverage and accuracy meet or beat the in-tree - pipeline, open a second PR that: - - flips the default in the tutorial, - - emits `DeprecationWarning` from - `fuzzy_kcat_matching` and `run_dlkcat`, - - schedules removal for the next minor. -4. Removal PR after a release cycle. diff --git a/src/geckopy/gather_kcats/open_kinetics_predictor/__init__.py b/src/geckopy/gather_kcats/open_kinetics_predictor/__init__.py index 9bece84..5ccf3fb 100644 --- a/src/geckopy/gather_kcats/open_kinetics_predictor/__init__.py +++ b/src/geckopy/gather_kcats/open_kinetics_predictor/__init__.py @@ -1,7 +1,6 @@ """Submit/fetch kcat predictions via the OpenKineticsPredictor REST API. Replaces the manual write-CSV -> upload -> download -> parse workflow. -See ``docs/internal/openkineticspredictor_plan.md``. """ from __future__ import annotations