Skip to content

geckopy 4.0.0b2 - #83

Merged
edkerk merged 58 commits into
mainfrom
merge/main-into-release
Nov 2, 2026
Merged

edkerk merged 58 commits into
mainfrom
merge/main-into-release

Conversation

@edkerk

@edkerk edkerk commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Changes since 4.0.0b1. The CHANGELOG entry 4.0.0b2 lists every change with its reason.

Changes that alter results or raise errors

  • The default kcat preset is brenda_ec_tree (#67), and the BRENDA filter for implausible SA-derived kcats applies with a default top_origin_limit of 4, down from 6 (#58).
  • get_enzyme_bottlenecks ranks the enzymes by the re-solved change of the objective (#77); get_conc_control_coeffs takes the finite-difference coefficients by default (#76); pfba_enzymes works for a negative or minimised objective (#81).
  • Every solve returns the infeasible solution instead of raising (#75).
  • Several functions raise a ValueError where they skipped a value with a warning, and results agree with GECKO for subsets, subsystems, uniprot cells, FSEOF, the f factor, the KEGG database and the solver tolerance (#72, #74, #78).
  • raven-toolbox>=3.0.0b2 is required.

Added

  • Gathered kcats are kept as kcat source files and assigned as model_adapter.toml sets (#64, #66); model_adapter.toml is shared with GECKO (#61).
  • BRENDA data from toolbox-assets and upgrades of GECKO 3 projects (#68); kcats estimated from the EC tree (#67).
  • Complex levelling, per-complex relaxation and relax_enz_concs (#63).
  • Saved ecModels record the ecModel format and the geckopy release (#69); sweep_tunable_mask_size and recommend_target_impact_share (#59); the scipy solver is supported (#72).

Fixes

Complex Portal search paging and the corrections() example (#60); CatRange predictions returned as class ranges (#62).

Repository

Links point to CompMetEng (#65) and the README is shorter, with the protocol citation (#79).

kcat.tsv/sa.tsv/mw.tsv (and the ecTestGEM test fixtures) are now
kcat.tsv.xz/sa.tsv.xz/mw.tsv.xz -- load_brenda_data and the
brenda-refresh CLI read/write through lzma transparently. Cuts the
wheel from 1.65MB to 1.28MB; the sdist is unaffected (gzip already
compresses this repetitive tabular data over the whole tar stream
about as well as standalone xz does per-file, since it gets a much
larger compression window that way).

Also fixes scripts/kcat_aggregation_compare.py: a hardcoded
/mnt/c/Work/GitHub/geckopy path (broken outside one specific WSL
setup) replaced with a path relative to the script, and drops an
unused cobra import. Neither scripts file is covered by CI's ruff
check (scripts/ isn't in its path list), which is how this went
unnoticed.
Reverts the previous approach (committing kcat.tsv.xz/sa.tsv.xz/
mw.tsv.xz directly) back to plain-text TSVs in the repo -- diffable,
and what geckopy brenda-refresh regenerates. A new hatchling build
hook (hatch_build.py), scoped to the wheel target only, compresses
them into the wheel at build time; the raw .tsv files are excluded
from the wheel via pyproject.toml. brenda_loader.py now looks for the
.tsv.xz variant first and falls back to plain text, so it works
against an installed wheel, an editable install, or a freshly
regenerated dev checkout without caring which.

Verified end-to-end: built the wheel, installed it into a clean venv,
confirmed only the .xz files are present on disk and load_brenda_data
reads them correctly (39,318 kcat rows). sdist keeps the plain-text
files, matching git. Wheel: 1.28MB either way; sdist: 6.46MB (was
6.39MB pre-xz -- the extra ~70KB is hatch_build.py plus a few bytes
of pyproject.toml diff, not the data).
Was Path(data) / brenda / _cache, a bare relative path that
resolves against whatever the CLI's cwd happens to be at invocation
time -- which is how src/geckopy/data/brenda/_cache/ ended up holding
a stray download in this checkout (brenda-refresh was run from
src/geckopy/ once). The raw bulk-JSON download is exactly the kind of
disposable, multi-hundred-MB, re-fetchable artifact raven-toolbox
already has a convention for (raven_toolbox.binaries._cache_dir:
$XDG_CACHE_HOME or ~/.cache); this matches it. --out-dir (the
aggregated TSVs, a chosen output rather than a cache) is unchanged.
…g MATLAB

Within one EC number the six levels are tried in the order 1, 2, 3, 5,
4, 6 (a correct-organism specific activity before an any-organism
any-substrate kcat), as in fuzzyKcatMatching.m. When a reaction has
several EC numbers, fuzzyKcatMatching.m compares the matches in that
same search order; geckopy compared the output origin numbers instead,
so origin 4 beat origin 5. SEARCH_RANK, derived from SEARCH_LEVELS, now
drives the selection across EC numbers too.

Effect on the yeast-GEM tutorial: r_0005 (EC 2.4.1.34;3.6.5.2) got
0.0005 /s from a human GTPase value for the second EC, where MATLAB
takes 13.3 /s from the S. cerevisiae specific activity for the first;
r_0510 likewise (0.018 vs 603 /s). r_0005 alone held the pre-tuning
growth rate at 0.0013 /hour; with this change it is 0.12 /hour, as in
MATLAB.
cobra.io.load_yaml_model drops RAVEN-specific reaction fields, including
the per-reaction eccodes list that RAVEN YAML uses for EC numbers, so a
yeast-GEM loaded this way had no EC numbers at all and
fill_eccodes_from_gem returned empty codes for every reaction.
read_yaml_model lifts eccodes into annotation['ec-code'] and also reads
plain cobra-style YAML, so it replaces cobra's reader for .yml/.yaml.
fuzzy_kcat_matching needs the KEGG phylogenetic distances to pick the
closest organism when BRENDA has no value for the model's own organism.
MATLAB reads them from RAVEN's keggPhylDist.mat; download_phyl_dist
fetches that file (pinned to RAVEN 3.0.0b1) to a given path, typically
adapter.get_phyl_dist_path(). The file is validated with load_phyl_dist
before it replaces the destination.
…ht vs full

- STEP 16-17: fill_eccodes_from_database gets kegg_db, as MATLAB's
  getECfromDatabase consults KEGG where UniProt has no or only an
  incomplete EC number.
- STEP 18-19: data/PhylDist.mat was a two-organism stub, so the "closest
  organism" levels fell back to arbitrary values (r_0103_REV took
  5e6 /s from Cupriavidus necator where MATLAB takes 3 /s from
  Aspergillus fumigatus). The tutorial now downloads RAVEN's
  keggPhylDist.mat on first run; the file is git-ignored.
- STEP 26: merge_kcats replaces the deprecated
  merge_dlkcat_and_fuzzy_kcats.
- STEP 43-44: the note about the unported ABC-SMC tuning is replaced by
  a pointer to evotune, not run, as in the MATLAB tutorial.
- STEP 73-75: ecFVA runs (skipped on GLPK). The commented-out version
  ran ec_fva on the ecModel for the "bare GEM" column, duplicating the
  ecModel result.
- STEP 76-77: code/plot_light_vs_full.py ports plotlightVSfull.m; light
  and full both reach 0.0280 /hour, as in MATLAB.
- model_adapter.toml: max_growth_weight is applied to the max-growth
  term in both MATLAB and geckopy; the comment said otherwise.

Together with the fuzzy_kcat_matching fix, the tutorial now tracks the
MATLAB protocol: 7 kcats tuned (growth 0.428 vs 0.427 /hour), 0.0129
/hour after flux constraints (MATLAB 0.0129) and 123.75 mg/gDCW minimum
pool usage (identical).
- EvotuneParams.max_growth_weight: MATLAB applies maxGrowthWeight to the
  max-growth term, like geckopy, so no conversion is needed.
- prior_penalty_weight comment: GECKO 4's cmaesKcatTuning has the same
  priorPenaltyWeight term, so reproducing MATLAB does not require 0.
- Migration guide: download_phyl_dist in the kcat example, and YAML
  starting GEMs load through raven-toolbox.
- CHANGELOG: Unreleased section.
The three links to the "MATLAB ↔ Python translation guide" at
gecko-docs.readthedocs.io/en/latest/api/translation/ return 404; that
page does not exist on gecko-docs. The per-function differences they
refer to are on the "GECKO vs. geckopy" page (gecko-to-geckopy.html).
…tutorial

Match MATLAB kcat selection; full PhylDist, ecFVA and light vs full in full_ecModel tutorial
classify_kcat_source compared the whole ec.source string against each
source_groups entry. geckopy writes "brenda (wc=0, origin=1)" where
MATLAB writes "brenda", so every fuzzy database match missed its group
and fell through to unlabelled, taking sigma0_log_default rather than
the tier's width: 3497 of ecYeastGEM's 4671 kcats, which is the whole
point of having tiers.

Matching now ignores the bracketed fuzzy-match detail. A source_groups
entry that lists the full string still matches it, so a project can
still single out, say, wildcarded matches.

This changes tuning results on any model whose kcats came from the fuzzy
matcher.
cobra returns an annotation value as the object it was assigned, and as
a list for a model read back from a file. A metabolite annotated in
memory by find_met_smiles carries "CCO"; the same metabolite after
save_ec_model + load_ec_model carries ["CCO"].

extract_enzyme_substrate_pairs -- which both write_dlkcat_input and the
OpenKineticsPredictor input builder go through -- assumed the string. On
a reloaded model the ignore-list membership test hashed a list and the
whole step died with TypeError, and had it not, the list would have been
written into the DLKcat input in place of the SMILES.

The tutorial does not hit this because it annotates and writes in one
session, but it documents reloading from the saved stage-1 model between
those steps, which is enough to reach it.

databases.met_smiles reads either shape and is the one place that knows
the difference.
A BRENDA kcat is a turnover per molecule of one named substrate, so it
becomes a reaction turnover by dividing by that substrate's coefficient.
The division used the smallest coefficient in the reaction instead.

Where a reaction is not written per the measured substrate, that inflates
the kcat. yeast-GEM writes cytochrome c oxidase per one cytochrome c, so
oxygen carries 0.25, and BRENDA's S. cerevisiae value of 1500/s became
6000/s in the full_ecModel tutorial.

26 of 4671 matched reactions change there, all previously inflated, by
factors of 2, 3 and 4. None changes which BRENDA row or match level it
draws from.
…tching

Apply evotune trust tiers to fuzzy-matched kcats
`parse_okp_output`: indexing metabolites by `annotation['smiles']` used
the annotation value as a dict key, which raises
`TypeError: cannot use 'list' as a dict key` on a saved-and-reloaded
model, the same shape mismatch `met_smiles` already handles for the
DLKcat input writer.
…d-trip

Read a metabolite SMILES annotation whatever shape it is in
…-coefficient

Divide a BRENDA kcat by the substrate it was measured on
… 6 -> 4 (#58)

* Document BRENDA 2026.1 data trap, floor fix, and the DLKcat/floor interaction

Investigates every difference between the June-2026 ecYeastGEM build and
mainline geckopy today: matcher bug fixes, standard-kcat scope, OKP
tiering, BRENDA 2026.1, and prediction source (CatPred/DLKcat/EITLEM).
Six changes tune to the same place as June within noise; DLKcat and
EITLEM both improve on it. BRENDA 2026.1 breaks tuning outright via a
specific-activity/molecular-weight data trap (EC 2.3.1.297), traced to
a mouse row surviving a dropped yeast row in the SA-MW join. Two
mitigations compared (top_origin_limit=4, and a relative floor on
derived kcats); the floor alone gives the best results for three of
four predictors but breaks DLKcat via a Crabtree-effect/overflow
mismatch, traced by comparing per-condition simulated vs measured
ethanol secretion. Combining both mitigations is clean for every
predictor tested.

* Note that floor+limit4 is redundant with limit4 alone for CatPred

c3_fl4 vs c3_o4 differ in only 34 of 4834 kcats; b26_fl4 vs b26_o4 are
byte-identical. Not a bug: top_origin_limit=4 already demotes every
origin-5/6 BRENDA match below any prediction, and CatPred's coverage
is broad enough that almost every such reaction already has a CatPred
prediction to fall back to. The floor only has room to matter where a
predictor's coverage is patchier (DLKcat, EITLEM).

* Port the BRENDA outlier floor and default top_origin_limit=4

Two changes, meant to ship together (see docs/internal/pipeline_deltas_since_june.md):

- load_brenda_data gains filter_outlier_kcats (default True): drops a
  kcat more than 4 orders of magnitude below the median of its own EC
  family (widening to a broader EC prefix when fewer than 5 reference
  values are available), computed from the union of the plain-kcat and
  SA-derived tables. Catches cases like EC 2.3.1.297, where the only
  molecular-weight row available is for the wrong organism and the
  resulting SA x MW join gives a kcat 8 orders of magnitude below a
  same-EC measurement that has no MW row and would otherwise be
  silently dropped instead of used.

- merge_kcats' and merge_dlkcat_and_fuzzy_kcats' top_origin_limit
  default changes from 6 to 4, so a specific-activity-derived BRENDA
  match (origin 5-6) no longer automatically outranks a prediction.
  Redundant with the filter above when a predictor's coverage is
  broad (e.g. CatPred), but still needed on its own where coverage is
  patchier (DLKcat, EITLEM) -- see the doc for the Crabtree-effect
  mismatch this prevents when paired with the floor alone.

Both changes are opt-out (filter_outlier_kcats=False,
top_origin_limit=6 explicit) rather than removed, so the pre-existing
behaviour stays reachable. Full test suite passes (1435 passed, 1
xfailed, 0 failed); added dedicated tests for the floor filter and
updated the one test whose name and assertion encoded the old
top_origin_limit default as intentional behaviour.

* Drop the internal pipeline-deltas writeup from this PR

Investigation notes, not part of the public change.
select_tunable_mask's target_impact_share picks a comparable *share*
of leverage on any model, but the same share does not sit at a
comparable point on the *fit* curve -- two models with similarly
shaped leverage curves can have fit curves that diverge sharply. The
only way to tell is to tune at a few candidate sizes and look at
where the improvement stops.

sweep_tunable_mask_size tunes at each of a list of target_impact_shares
(and seeds) and reports the resulting fit per point.
recommend_target_impact_share picks the smallest share past which
further tuning stops paying for itself, using two independent checks
against a later point's best result: a relative-improvement tolerance,
and, where at least one of the two points compared has more than one
seed, a check against their own local seed spread (not a sweep-wide
pooled one, since how noisy a comparison is can differ across the
sweep).

MaskSizePoint.rmse takes min(rmse_trace) per seed rather than the
final entry, since rmse_trace only advances at a step that improved
the (possibly prior-penalised) objective, per cmaes_kcat_tuning's own
docstring -- with the default nonzero prior_penalty_weight the last
entry is not guaranteed to be the best one recorded.

sweep_tunable_mask_size's default target_impact_shares now starts at
0.3 (previously 0.5) and its default seeds is (0, 1) (previously a
single seed), so a default call's noise-floor check has something to
compare against instead of silently degrading to the relative check
alone.
…ple (#60)

* complex_portal_download: page the search by record offset and total

The Complex Portal search endpoint reports `size` as the number of
elements in the page at hand and `totalNumberOfResults` as the grand
total, and `first` counts records rather than pages. Reading `size` as
the total ended the paging loop after the first page, so any organism
with more than 100 complexes was silently truncated: S. cerevisiae
(taxonomy 559292) returned 100 of its 634.

Page by record offset, take the total from `totalNumberOfResults`, and
warn rather than truncate silently if the page bound is ever reached.
The test fixtures now use the real field semantics.

* evotune docs: corrections() does not take annotate_from_model's sources

`annotate_from_model` returns names, ec_codes and sources, but
`EvotuneResult.corrections()` fills sources from `result.groups`
itself and has no such keyword, so the documented
`result.corrections(leverage=leverage, **annotations)` raises
TypeError. Spell the two keywords it does take, and say where the
third one goes.
GECKO (MATLAB) now reads model_adapter.toml as well. Two sections make that
possible without code in an adapter class:

- [spontaneous]: name_contains and/or file set which reactions are
  spontaneous; the default get_spontaneous_reactions reads them.
- [matlab]: settings that only GECKO reads (function names for anaerobic
  conditions and protein content, and [matlab.params]); geckopy accepts and
  ignores them.

adapterParameters.tsv, the table GECKO uses to map keys to its MATLAB
field names and defaults, is shipped here too, and a test checks it
against the schema.
CatRange predicts a class of kcat values, which OpenKineticsPredictor
returns as its range ("10 to 100 s^-1") in the column "Predicted kcat
range: kcat (1/s)". parse_okp_output required a column named exactly
"kcat (1/s)" and so rejected CatRange results. It now takes the one
column ending in "kcat (1/s)" and reads a range as its geometric
midpoint, or half the upper bound for the class starting at 0. The list
of available predictors is updated.
* feat: level complex abundances and flexibilize per complex

level_complex_concs (and level_complex_abundances for molar data) gives
every enzyme complex one abundance, the median per copy over its
measured subunits, so a single badly quantified subunit no longer sets
the complex's capacity. complex_families groups isozyme variants into
one complex and leaves subunits shared between complexes as measured.
flexibilize_enz_concs(per_complex=True) raises all subunits of a
limiting complex by the same factor. See raven-gecko-parity#131.

* fix: keep carriers out of complexes and relax per complex without lowering caps

Two enzyme sets merge only when they share two or more subunits and half
of the larger set, so electron carriers no longer chain complexes
together; a subunit in two complexes belongs to the one with more
reactions. per_complex ranks complexes by summed control coefficients,
counts iterations per complex and never lowers a cap.

* fix: rank units by the cap duals, and probe with the pool lifted when no dual is positive

* feat: relax_enz_concs, the least total relaxation of enzyme caps in one LP

* docs: describe relax_enz_concs and the complex options
Gathered kcats are written to data/kcats/<source>.tsv in the project,
keyed by conventional reaction and enzyme set rather than model.ec.rxns,
so a file stays valid when the ecModel is rebuilt and applies to full and
light models alike. GECKO reads and writes the same files.

- kcats_from_brenda, kcats_from_dlkcat and kcats_from_okp gather one source
  each, with that source's own options, and write its file. OKP keeps a
  job and a result per method, and only uses a stored result of the
  current job.
- save_kcat_source writes any kcat list; read_kcat_source reads a file;
  load_kcat_source maps a file to a model and reports rows with an invalid
  kcat, rows that fit no reaction, and rows that match only part of an
  enzyme set.
- ec_rxn_keys gives the conventional reaction and enzyme set of every
  model.ec reaction.
- fuzzy_kcat_matching takes an explicit adapter, and
  fetch_open_kinetics_predictor the file to store the result in.
)

* Share model_adapter.toml with GECKO

GECKO (MATLAB) now reads model_adapter.toml as well. Two sections make that
possible without code in an adapter class:

- [spontaneous]: name_contains and/or file set which reactions are
  spontaneous; the default get_spontaneous_reactions reads them.
- [matlab]: settings that only GECKO reads (function names for anaerobic
  conditions and protein content, and [matlab.params]); geckopy accepts and
  ignores them.

adapterParameters.tsv, the table GECKO uses to map keys to its MATLAB
field names and defaults, is shipped here too, and a test checks it
against the schema.

* Keep gathered kcats as kcat source files, one per source

Gathered kcats are written to data/kcats/<source>.tsv in the project,
keyed by conventional reaction and enzyme set rather than model.ec.rxns,
so a file stays valid when the ecModel is rebuilt and applies to full and
light models alike. GECKO reads and writes the same files.

- kcats_from_brenda, kcats_from_dlkcat and kcats_from_okp gather one source
  each, with that source's own options, and write its file. OKP keeps a
  job and a result per method, and only uses a stored result of the
  current job.
- save_kcat_source writes any kcat list; read_kcat_source reads a file;
  load_kcat_source maps a file to a model and reports rows with an invalid
  kcat, rows that fit no reaction, and rows that match only part of an
  enzyme set.
- ec_rxn_keys gives the conventional reaction and enzyme set of every
  model.ec reaction.
- fuzzy_kcat_matching takes an explicit adapter, and
  fetch_open_kinetics_predictor the file to store the result in.

* Assign kcats from the kcat source files, as model_adapter.toml sets

collect_kcats decides one kcat per reaction from the files in data/kcats,
in the stages of [kcat_assignment] (a preset, or [[kcat_assignment.stage]]
tables), without changing the model, and writes
output/<model id>_kcatDecisions.tsv. assign_kcats applies the decisions and
the kcat constraints. update_kcat_sources gathers the source files the
configuration needs that are missing, or outdated against
[kcat_sources.<name>].

The default preset, gecko_median, orders custom kcats, the BRENDA median
of matches without EC wildcards at origins 1 to 4, DLKcat, then the other
BRENDA matches. gecko_classic keeps the GECKO 3 order with the BRENDA
maximum. The full_ecModel tutorial gives the same model as before, with
the gecko_median stages and the BRENDA maximum.

* Name GECKO's kcat source functions as they are now
…et (#67)

measurements.tsv, next to the BRENDA kcat, sa and mw files, holds the
wild-type BRENDA kcats with an EC number after the outlier screen, one row
per measurement. kcats_from_ec_tree gives each reaction the median kcat of
its EC number, shrunk toward its 3- and 2-digit EC classes with weight
n / (n + k), and keeps it as data/kcats/ec_tree.tsv (ECTree,
load_brenda_measurements). save_kcat_source takes further detail pairs from a
detail column.

The brenda_ec_tree preset, now the default, orders custom kcats, the
BRENDA median of measurements of the organism on the reaction's substrate
(origin 1), the EC tree, then DLKcat for reactions without an EC number.
update_kcat_sources gathers ec_tree.tsv and marks it outdated for another k
or BRENDA release.
edkerk added 28 commits October 19, 2026 09:07
…des (#68)

* Download the BRENDA files from toolbox-assets instead of shipping them

fetch_brenda_asset downloads the files of the toolbox-assets release
brenda-2026.1-1 (kcat, sa, mw and measurements, and on request sequences,
substrates and combination parameters) with raven-toolbox's checksummed
download, and unpacks them in its cache. ModelAdapter.get_brenda_db_folder
gives the project's data/brenda when it exists and these files otherwise.
The wheel no longer carries BRENDA files, so the build hook that compressed
them is gone.

* Combine kcat sources; presets for OKP; OKP replaces DLKcat in the default

A combine stage gives each open reaction the precision-weighted mean of
its sources' values, each corrected for the source's bias and weighted by
1 / its error variance for the source's evidence (EC tree: measurements and
EC level; OKP method: similarity to its training sequences), with an
uncertainty for a new measurement. The parameters are kcatCombineParams.tsv
of the BRENDA files. model.ec.source reads 'combined (sd=..., n=...)' and
the decisions get sd_log10.

okp in a stage stands for the OKP methods of [kcat_sources.okp] methods.
OKP results keep the similarity to the method's training data, written as
similarity= in the detail column, and load_kcat_source passes the detail on.

Presets: brenda_ec_tree combines the OKP methods for the reactions without
an EC number instead of using DLKcat; ec_tree_okp combines the EC tree with
the OKP methods; okp uses the OKP methods only.

* Convert custom kcats; upgrade GECKO 3 projects and ecModels

convert_custom_kcats writes data/customKcats.tsv as the curated kcat source
file data/kcats/custom.tsv: the kcats apply_custom_kcats gives the ecModel,
one row per reaction keyed by conventional reaction and enzyme set.
upgrade_ec_model turns the protein usage and pool reactions of a GECKO 3
ecModel forward. upgrade_project (geckopy upgrade) lists, and with dry_run=False
carries out, the steps from a GECKO 3 project: custom kcats, stored
predictions to source files, saved ecModels, project_format = 4; it
lists script lines that call renamed or replaced functions. Originals
go to legacy/<date>/. kcats_from_brenda downloads PhylDist.mat when missing.
model_adapter.toml takes project_format. The full_ecModel tutorial shows
how to choose a kcat strategy.

* Estimate kcats from measured homologs

kcats_from_homologs searches each enzyme of a reaction with DIAMOND (raven-toolbox's)
against the sequences of the BRENDA measurements (at least 40% identity,
80% coverage of both), and takes the identity-weighted mean of the medians
of up to 10 homologs measured on the reaction's substrate, matched by
InChIKey through ChEBI or MetaNetX annotations, or else the name. It writes
data/kcats/homologs.tsv with the nearest identity and the number of
homologs in the detail column. extract_enzyme_substrate_pairs gives the
substrates and now their met_id. The ec_tree_homologs preset
combines the EC tree with homologs, and ec_tree_okp combines homologs too;
update_kcat_sources gathers homologs.tsv.

* Treat homologs at 95% identity and above as the same protein

When a homolog at 95% identity or more was measured on the substrate,
the homolog estimate is the median of those homologs alone, and the
combination gives it the exact bin of kcatCombineParams.tsv: no bias and
the smallest error variance, an assumption, as the benchmark the other
bins were fitted on hides such homologs. The detail column writes the
identity with three decimals, as DIAMOND reports it, so that it falls in
the right bin.

* Use OKP's TurNup name; reaction-level OKP methods are not run per enzyme-substrate pair

OpenKineticsPredictor names the method TurNup. CatPred and TurNup take the
substrates (and products) of a whole reaction, so the default OKP methods
are the eight pair-based ones with combination parameters, and asking for
CatPred or TurNup per pair is an error.

* Write the enzyme-usage text as RAVEN's metabolite note

makeEcModel puts it in metNotes. As a separate notes key it was written as a
nested map, which RAVEN's readYAMLmodel cannot read, so GECKO could not load
an ecModel that geckopy saved.
…#69)

save_ec_model writes gecko_format and generator instead of geckopy_version
and keeps the model's date. load_ec_model warns about a GECKO 3 ecModel
naming upgrade_project and refuses a format it does not know.
Part of CompMetEng/GECKO#5.
…ze target and light-model isozymes

find_ec_in_db splits a database EC cell on semicolons and whitespace, so UniProt cells ("1.1.1.1; 2.2.2.2") and KEGG cells ("1.1.1.4 1.1.1.-") give one code per token.

enzyme_usage gives a NaN capacity for an enzyme whose usage upper bound is 0, as Enzyme.cap_usage does.

flexibilize_enz_concs takes the growth it reached as the target of the final pass and of the reported model when no enzyme limits growth any more.

apply_kcat_constraints on a gecko-light model recomputes the cheapest cost over all isozyme rows of a reaction when one of its rows is named.
…onfirmed by a small raise of the bound, in place of the prot_<id> shadow price
… kcat source file, and the defaults of fill_kcats_from_isozymes
…ning

fit_sigma scans the 0.01 grid by default, gives the first sigma of the growth plateau for a target growth cannot reach, and opens the bounds of the objective reaction. sensitivity_tuning lists the tuned reactions in model order, reports the kcat and source of the input model, prints the pre-tune kcat as num2str does, matches prot_to_ignore without regard to case and raises for an unknown id, has no upper limit on a tuned kcat and tunes a gecko-light model.
… fit_sigma bisect gives the first grid sigma for a model that cannot grow
…usage reaction held at its lower bound; tests for the verification rule
…and apply_custom_kcats reads a quoted reaction list, as GECKO does
…ible non-exchange reactions; set_kcat_for_reactions follows setKcatForReactions
…arginal with the scipy solver, in place of the finite-difference loop
…no reduced costs; set_kcat_for_reactions takes a per-match kcat list by position even when an ID matches nothing
fix: give the same result as GECKO where the parity scenarios found differences
…nd pFBA; scipy solver support (#72)

* Decide an infeasible start from the solver status in flexibilize_enz_concs and sensitivity_tuning

* Support the scipy solver: repair optlang's variable index map, read duals from HiGHS, build large models sparsely

optlang's scipy interface renumbers variable columns wrongly after a removal and keeps a dense matrix that it copies on every edit. An EcModel now moves its scipy problem to a sparse class with correct renumbering whenever model.solver is read, Enzyme.shadow_price, get_enzyme_bottlenecks and relax_proteomics_greedy read HiGHS row marginals on scipy, and load_conventional_gem and make_ec_model build on glpk and convert to scipy at the end.

* Return a solution with its status after an infeasible scipy solve; test the flexibilize start on scipy

* Remove the genes of get_subset_ec_model with cobra's remove_genes and keep the ec rows of a light model

A reaction that survives the removal of a gene of its rule lost the gene from model.genes but not from its rule, so removing it later raised that the gene was not found. The ec rows of a gecko-light model carry a 001_ prefix and were all dropped.

* Compare subsystem names in assign_standard_kcat without regard to letter case

* Read the cells of uniprot.tsv and uniprotConversion.tsv like textscan with %q

* Load ecModel files with an empty enzymes map and unescaped quotes in a value, as GECKO's loadEcModel does

* Remove the metabolites and genes no reaction uses from a subset ecModel

getSubsetEcModel's removeGenes and removeReactions calls drop every
metabolite and gene without a reaction. get_subset_ec_model left the
pseudo-gene standard and six unused metabolites in the HT29-GEM subset of the
light Human-GEM ecModel.

* Load an ecModel and set its solver to scipy without optlang's dense clone

load_ec_model reads the file on glpk and converts the ecModel when scipy is
cobra's default solver, and setting EcModel.solver to scipy builds the sparse
problem of switch_to_scipy. Loading ecYeastGEM on scipy did not finish in 25
minutes before.

* Copy and pickle a scipy ecModel into the sparse problem

optlang restores a pickled or copied scipy model by adding every variable and
constraint to a dense problem, which took tens of minutes per ec_fva worker on
ecYeastGEM.

* Pass model.tolerance to HiGHS on the scipy solver

optlang's scipy interface ignores every tolerance, so a model set to 1e-9 was
solved at the HiGHS default of 1e-7.

* Solve a scipy LP again at the default tolerance after numerical difficulties

* Fix the objective at 0.999999 of its optimum in pfba_enzymes, as getPfbaEnzymes does

* Fix the target flux in ec_fseof and list reaction targets before transport targets, as ecFSEOF does

* Edit a scipy ecModel on glpk in get_subset_ec_model and apply_kcat_constraints, and populate the sparse problem in one pass

optlang's scipy interface removes constraints through a loop that calls the generic
removal once per constraint with the whole list, so removing two or more constraints
raised LookupError; the sparse model removes them correctly. Removals, and the
coefficient rewrites of apply_kcat_constraints, run on glpk and the model is converted
back with switch_to_scipy, which now creates each constraint with its whole expression.

* Use colons in the new load_ec_model docstring items

* Install raven-toolbox from develop in the test workflow
* Check the starting LP of flexibilize_enz_concs and sensitivity_tuning from the solver status after slim_optimize

* optimize_model returns the infeasible solution on every solver; the limit-protein, kcat-tuning and analysis solves use it
)

* make_ec_model reads data/kegg.tsv when no kegg_db is passed

* Models loaded by load_conventional_gem carry RAVEN's solver tolerance

* calculate_f_factor returns NaN for a zero total, as calculateFfactor does
…efault, as getConcControlCoeffs does (#76)

method="shadow_price" keeps the reduced-cost path.
…#77)

* Rank get_enzyme_bottlenecks by the re-solved change of the objective

The order no longer depends on the shadow price, which depends on the basis at a degenerate optimum. Enzymes at their bound are solved again with the bound raised by fold_change; multiple_active marks rows where another capacity constraint is active.

* get_enzyme_bottlenecks reports the gain in the objective's direction and requires a fold_change above 1

* get_enzyme_bottlenecks logs a warning when no enzyme is at its usage bound

* Migration notes: no dash in the get_enzyme_bottlenecks bullet
* Rank get_enzyme_bottlenecks by the re-solved change of the objective

The order no longer depends on the shadow price, which depends on the basis at a degenerate optimum. Enzymes at their bound are solved again with the bound raised by fold_change; multiple_active marks rows where another capacity constraint is active.

* get_enzyme_bottlenecks reports the gain in the objective's direction and requires a fold_change above 1

* get_enzyme_bottlenecks logs a warning when no enzyme is at its usage bound

* Migration notes: no dash in the get_enzyme_bottlenecks bullet

* Evotune: skip NaN rmse, stable screen order, full generations on a flat objective

best_parsimonious ignores points with a NaN rmse, screen_kcat_leverage sorts
stably, cmaes_kcat_tuning does not stop on a flat generation, and
compute_excarbon warns for a model without metabolite formulas.

* Export review_assignment, EcStats and ec_stats_from_brenda

* fill_enz_concs and constrain_enz_concs raise for a negative concentration

* load_flux_data raises for a ragged row and reads nested parentheses in headers

* enzyme_usage warns about enzymes without usage reaction; report_enzyme_usage rejects a negative top_abs_usage

* Add tests for the shared contract of calculate_f_factor, relax_proteomics_greedy, apply_complex_data and add_new_rxns_to_ec

* Add the CHANGELOG entries of round 5 group 3

* read_kcat_source skips a row of empty quoted fields and raises for a field with an opening quote and no closing one

* read_dlkcat_output drops infinite kcats; write_dlkcat_input writes the rows in the order of model.ec.rxns

* ECTree raises for k = NaN; kcats_from_ec_tree takes an empty ec_rxns as no reaction

* assign_standard_kcat leaves out reactions without a substrate or without a product as exchange reactions

* save_ec_model keeps an existing metaData description

The default description is written only when the model has none, as
saveEcModel does.

* review_assignment recognises the sources custom and standard without regard to case

* find_met_smiles skips metabolites without a name with a warning

* Changelog: source case in review_assignment, unnamed metabolites in find_met_smiles

* pfba_enzymes multiplies a given fraction_of_optimum by 0.999999

* apply_kcat_constraints takes cobra reaction ids and ec ids on a gecko-light model

* constrain_enz_concs resets the lower bound and the pool coefficient of a usage reaction

* Add the CHANGELOG entries for pfba_enzymes, apply_kcat_constraints and constrain_enz_concs

* geckopy.kcat_tuning.evotune exports the parsimony and isozyme-tying helpers

* save_ec_model: write no enzymes entry for an ec reaction without enzymes

Tests cover the round trip, the saved text and the light tutorial ecModel. The format page and the docstrings describe the optional enzymes entry.

* ec_fseof orders targets by the score rounded to 9 significant digits

* load_ec_model: comment on the criterion of the reader
* README: shorter, with the GECKO logo, the parity ledger and the protocol citation

* README: link the gecko-docs pages first, badges for documentation and support
@edkerk
edkerk merged commit d2347c9 into main Nov 2, 2026
8 checks passed
@edkerk
edkerk deleted the merge/main-into-release branch November 2, 2026 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant