Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
*.txt text eol=lf
*.xml text eol=lf
*.md text eol=lf

# A fixture that must keep its byte-order mark and CRLF line endings.
tests/data/kcatSourceFixture.tsv -text
8 changes: 8 additions & 0 deletions docs/migrating_from_gecko_matlab.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ Each row maps a MATLAB function to its geckopy equivalent. The
| `fetchOpenKineticsPredictor` | `fetch_open_kinetics_predictor` | **`[3→4]`** New REST-based fetch (replaced `readOpenKineticsPredictorOutput`); per-row `kcatSource` reports actual provenance (CataPro / BRENDA / Sabio-RK). |
| `writeOpenKineticsPredictorInput` | *(removed)* | **`[3→4]`** Replaced by `submit_open_kinetics_predictor`. |
| `readOpenKineticsPredictorOutput` | *(removed)* | **`[3→4]`** Replaced by `fetch_open_kinetics_predictor`. |
| `kcatsFromBrenda` | `kcats_from_brenda` | Fuzzy BRENDA matching, kept as `data/kcats/brenda.tsv`. Loads the BRENDA data and KEGG distances from the adapter unless passed in. |
| `kcatsFromDLKcat` | `kcats_from_dlkcat` | DLKcat (run in Docker with `run=True`, or an earlier output), kept as `data/kcats/dlkcat.tsv`. |
| `kcatsFromOKP` | `kcats_from_okp` | One OpenKineticsPredictor method, kept as `data/kcats/okp_<method>.tsv`; submits a job when there is none, writes the file once it is done. Returns `(kcat_list, done)`. |
| `saveKcatSource`, `readKcatSource`, `loadKcatSource` | `save_kcat_source`, `read_kcat_source`, `load_kcat_source` | Kcat source files: one per source, keyed by conventional reaction and enzyme set, the same file format in both tools. `load_kcat_source` maps a file to the current model and reports rows that fit no reaction. |
| `getEcRxnKeys` | `ec_rxn_keys` | Conventional reaction and enzyme set per `ec.rxns` entry. |
| `collectKcats` | `collect_kcats` | Decides one kcat per reaction from the source files, as `[kcat_assignment]` in model_adapter.toml sets (preset `gecko_median`, the default, or `gecko_classic`, or stages), without changing the model. Writes `output/<model id>_kcatDecisions.tsv`. |
| `assignKcats` | `assign_kcats` | `collect_kcats`, then sets `ec.kcat` and `ec.source` and applies the kcat constraints; replaces `merge_kcats` → `apply_kcat_list` → `apply_custom_kcats` → `fill_kcats_from_isozymes` → `assign_standard_kcat` → `apply_kcat_constraints`. In place. |
| `updateKcatSources` | `update_kcat_sources` | Gathers the source files the configuration needs that are missing, or outdated against `[kcat_sources.<name>]`; `dry_run` only reports. |

### 5.3 Get enzyme data (`src/geckomat/get_enzyme_data`)

Expand Down
18 changes: 18 additions & 0 deletions src/geckopy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,26 @@
# Gather-kcats
from geckopy.gather_kcats import (
apply_kcat_list,
assign_kcats,
assign_standard_kcat,
collect_kcats,
fetch_open_kinetics_predictor,
fuzzy_kcat_matching,
get_standard_kcat, # deprecated; alias of assign_standard_kcat
kcats_from_brenda,
kcats_from_dlkcat,
kcats_from_okp,
load_kcat_source,
merge_dlkcat_and_fuzzy_kcats, # deprecated; alias of merge_kcats
merge_kcats,
read_dlkcat_output,
read_kcat_source,
remove_standard_kcat,
run_dlkcat,
save_kcat_source,
select_kcat_value, # deprecated; alias of apply_kcat_list
submit_open_kinetics_predictor,
update_kcat_sources,
write_dlkcat_input,
)

Expand Down Expand Up @@ -183,15 +192,24 @@
"set_prot_pool_size",
# Gather-kcats
"apply_kcat_list",
"assign_kcats",
"assign_standard_kcat",
"collect_kcats",
"fetch_open_kinetics_predictor",
"fuzzy_kcat_matching",
"kcats_from_brenda",
"kcats_from_dlkcat",
"kcats_from_okp",
"load_kcat_source",
"merge_dlkcat_and_fuzzy_kcats", # deprecated; alias of merge_kcats
"merge_kcats",
"read_dlkcat_output",
"read_kcat_source",
"remove_standard_kcat",
"run_dlkcat",
"save_kcat_source",
"submit_open_kinetics_predictor",
"update_kcat_sources",
"write_dlkcat_input",
# get_enzyme_data
"copy_ec_to_gem",
Expand Down
29 changes: 25 additions & 4 deletions src/geckopy/adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ def from_folder(cls, folder: str | Path) -> "ModelAdapter":
data["path"] = folder
params = ModelParameters.model_validate(data)

# Resolve conv_gem relative to the folder if it is not absolute.
# Resolve project paths relative to the folder if they are not absolute.
if not params.conv_gem.is_absolute():
params.conv_gem = folder / params.conv_gem
if params.spontaneous.file is not None and not params.spontaneous.file.is_absolute():
params.spontaneous.file = folder / params.spontaneous.file

# Note: a subclass in the project's `adapter.py` is not auto-discovered;
# import and instantiate your ModelAdapter subclass explicitly.
Expand All @@ -101,10 +103,29 @@ def from_folder(cls, folder: str | Path) -> "ModelAdapter":
def get_spontaneous_reactions(self, model: "cobra.Model") -> list[str]:
"""Return IDs of reactions that proceed without enzyme catalysis.

The default returns an empty list. Override in a subclass to
identify spontaneous reactions for your organism.
The default reads the ``[spontaneous]`` section of the adapter:
reactions whose name contains ``name_contains``, and those listed
in ``file``. Without that section it returns an empty list.
Override in a subclass for other rules.
"""
return []
rules = self.params.spontaneous
if rules.name_contains is None and rules.file is None:
return []
listed: set[str] = set()
if rules.file is not None:
with open(rules.file, "r", encoding="utf-8") as f:
next(f) # header
for line in f:
parts = line.rstrip("\n").split("\t")
if not parts[0]:
continue
if len(parts) == 1 or parts[1].strip().lower() in ("1", "true"):
listed.add(parts[0].strip('"'))
return [
r.id for r in model.reactions
if r.id in listed
or (rules.name_contains is not None and rules.name_contains in (r.name or ""))
]

def get_uniprot_compatible_genes(self, in_genes: list[str]) -> list[str]:
"""Map model gene IDs to IDs usable in UniProt queries.
Expand Down
41 changes: 41 additions & 0 deletions src/geckopy/adapter/adapterParameters.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
toml_key matlab_field default description
conv_gem convGEM Path to the conventional GEM, relative to the project folder
org_name org_name Scientific name of the organism
sigma sigma 0.5 Average enzyme saturation factor
p_tot Ptot 0.5 Total protein content [g protein/gDW]
f f 0.5 Fraction of enzymes in the model [g enzyme/g protein]
gr_exp gR_exp 0.41 Growth rate the model should reach when not limited by nutrient uptake [1/h]
c_source c_source "" Exchange reaction of the main carbon source
bio_rxn bioRxn "" Biomass pseudoreaction
enzyme_comp enzyme_comp "cytoplasm" Compartment of the protein pseudometabolites
kcat_aggregate_brenda kcatAggregation "max" BRENDA aggregate read by fuzzyKcatMatching: max or median
kcat_aggregate_candidates "max" Aggregation of several candidate kcats for one reaction (geckopy)
kcat_aggregate_isozymes "mean" Aggregation of isozyme kcats when filling a missing value (geckopy)
kegg.id kegg.ID "sce" KEGG organism code
kegg.gene_id kegg.geneID "kegg" Field in the KEGG entry that matches model gene identifiers
uniprot.type uniprot.type "taxonomy" Whether uniprot.id is a taxonomy or a proteome identifier
uniprot.id uniprot.ID "559292" Taxonomy or proteome identifier
uniprot.gene_id_field uniprot.geneIDfield "gene_oln" UniProt field that matches model gene identifiers
uniprot.reviewed uniprot.reviewed false Use only reviewed (Swiss-Prot) entries
complex.taxonomic_id complex.taxonomicID Taxonomy identifier for Complex Portal
spontaneous.name_contains spontaneous.name_contains Reactions whose name contains this text are spontaneous
spontaneous.file spontaneous.file TSV of spontaneous reactions, relative to the project folder: reaction identifiers, optionally a 0/1 column
evotune.sigma0_log_default evotune.sigma0logDefault 0.5 Prior standard deviation (log) for kcats whose source is in no group
evotune.source_groups evotune.kcatSources Trust tiers: group name to the ec.source values it covers
evotune.sigma0_log_source evotune.sigma0logSource Prior standard deviation (log) per group
evotune.rmse_threshold evotune.rmseThreshold 0.2 Stop when the best RMSE reaches this
evotune.max_generations evotune.maxGenerations 150 Maximum number of CMA-ES generations
evotune.max_growth_weight evotune.maxGrowthWeight 1.0 Weight of the maximum-growth RMSE against the flux RMSE
evotune.prior_penalty_weight evotune.priorPenaltyWeight 0.03 Weight of the prior term in the search objective
evotune.tie_isozymes evotune.tieIsozymes true Give isozyme copies of one reaction a single kcat
okp.method okp.method "CataPro" OpenKineticsPredictor method
okp.handle_long_sequences okp.handleLongSequences "truncate" How OpenKineticsPredictor handles sequences that are too long
okp.include_similarity_columns okp.includeSimilarityColumns true Return similarity to the method's training data
okp.canonicalize_substrates okp.canonicalizeSubstrates true Canonicalize substrate SMILES before prediction
kcat_assignment.preset Kcat assignment preset: gecko_median (used when neither preset nor stages are given) or gecko_classic
kcat_assignment.isozymes true Fill reactions without a kcat from their isozymes after the stages
kcat_assignment.standard true Give the remaining reactions the standard kcat
kcat_assignment.stage Stages instead of a preset: action, sources, criteria and BRENDA limits ([[kcat_assignment.stage]])
kcat_sources Settings per kcat source file, e.g. [kcat_sources.brenda] aggregation
matlab.make_model_anaerobic matlab.makeModelAnaerobic MATLAB function that constrains the ecModel to anaerobic growth
matlab.change_protein_biomass matlab.changeProteinBiomass MATLAB function that changes the protein content of the biomass
101 changes: 100 additions & 1 deletion src/geckopy/adapter/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Ported from GECKO MATLAB: src/geckomat/model_adapter/adapterTemplate.m.
"""
from pathlib import Path
from typing import Literal, Optional
from typing import Any, Literal, Optional

from pydantic import BaseModel, ConfigDict, Field, model_validator

Expand Down Expand Up @@ -56,6 +56,98 @@ class ComplexParams(BaseModel):
)


class SpontaneousParams(BaseModel):
"""Which reactions proceed without an enzyme.

Both rules may be given; a reaction is spontaneous if either matches.
GECKO (MATLAB) reads the same keys, so a project needs no code for this.
"""
model_config = ConfigDict(extra="forbid")

name_contains: Optional[str] = Field(
default=None,
description="Reactions whose name contains this text are spontaneous.",
)
file: Optional[Path] = Field(
default=None,
description=(
"TSV of spontaneous reactions, relative to the project folder: "
"a header row, reaction identifiers in the first column and, "
"optionally, a second column where 1 or true marks the "
"spontaneous ones."
),
)


class MatlabParams(BaseModel):
"""Settings that only GECKO (MATLAB) reads; geckopy ignores them.

They let one model_adapter.toml configure both tools.
"""
model_config = ConfigDict(extra="forbid")

make_model_anaerobic: Optional[str] = Field(
default=None,
description="MATLAB function that constrains the ecModel to anaerobic growth.",
)
change_protein_biomass: Optional[str] = Field(
default=None,
description="MATLAB function that changes the protein content of the biomass.",
)
params: dict[str, Any] = Field(
default_factory=dict,
description="Further GECKO parameters, kept under their MATLAB names.",
)


class KcatStageParams(BaseModel):
"""One ``[[kcat_assignment.stage]]``: decides each reaction that is still
open by the first listed source with a value."""
model_config = ConfigDict(extra="forbid")

action: Literal["select"] = Field(default="select", description="What the stage does.")
sources: list[str] = Field(
min_length=1,
description=(
"Source file names (custom, dlkcat, okp_catapro, ...) and the BRENDA tiers "
"database_exact, database_top and database_bottom, which read brenda.tsv; "
"first listed wins."
),
)
criteria: Literal["max", "min", "median", "mean"] = Field(
default="max", description="Among several values of one source for a reaction."
)
top_origin_limit: int = Field(default=4, description="Origin limit of database_top.")
bottom_origin_limit: int = Field(default=6, description="Origin limit of database_bottom.")
wildcard_limit: int = Field(default=3, description="Wildcard limit of database_bottom.")


class KcatAssignmentParams(BaseModel):
"""How kcats are assigned from the kcat source files (``collect_kcats``)."""
model_config = ConfigDict(extra="forbid")

preset: Optional[Literal["gecko_classic", "gecko_median"]] = Field(
default=None,
description=(
"Kcat assignment preset; gecko_median is used when neither a preset nor "
"stages are given."
),
)
isozymes: bool = Field(
default=True, description="Fill reactions without a kcat from their isozymes after the stages."
)
standard: bool = Field(default=True, description="Give the remaining reactions the standard kcat.")
stage: list[KcatStageParams] = Field(
default_factory=list, description="Stages instead of a preset."
)

@model_validator(mode="after")
def _preset_or_stages(self) -> "KcatAssignmentParams":
if self.preset is not None and self.stage:
raise ValueError("[kcat_assignment] gives both a preset and stages; give one of the two.")
return self


class SourceGroupRule(BaseModel):
"""One trust-tier entry in ``EvotuneParams.source_groups``.

Expand Down Expand Up @@ -277,3 +369,10 @@ class ModelParameters(BaseModel):
complex: ComplexParams = Field(default_factory=ComplexParams)
evotune: EvotuneParams = Field(default_factory=EvotuneParams)
okp: OkpParams = Field(default_factory=OkpParams)
spontaneous: SpontaneousParams = Field(default_factory=SpontaneousParams)
kcat_assignment: KcatAssignmentParams = Field(default_factory=KcatAssignmentParams)
kcat_sources: dict[str, dict[str, Any]] = Field(
default_factory=dict,
description="Settings per kcat source file, e.g. kcat_sources.brenda.aggregation.",
)
matlab: MatlabParams = Field(default_factory=MatlabParams)
13 changes: 13 additions & 0 deletions src/geckopy/gather_kcats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Mirrors GECKO MATLAB's `src/geckomat/gather_kcats/` directory.
"""
from .fuzzy_kcat_matching import fuzzy_kcat_matching
from .kcat_assignment import assign_kcats, collect_kcats, update_kcat_sources
from .kcat_sources import ec_rxn_keys, load_kcat_source, read_kcat_source, save_kcat_source
from .kcats_from import kcats_from_brenda, kcats_from_dlkcat, kcats_from_okp
from .get_standard_kcat import assign_standard_kcat, get_standard_kcat
from .merge_dlkcat_and_fuzzy_kcats import merge_dlkcat_and_fuzzy_kcats
from .merge_kcats import merge_kcats, normalize_source
Expand All @@ -26,19 +29,29 @@
"OKPClient",
"OKPError",
"apply_kcat_list",
"assign_kcats",
"assign_standard_kcat",
"collect_kcats",
"ec_rxn_keys",
"extract_enzyme_substrate_pairs",
"fetch_open_kinetics_predictor",
"format_kcat_source",
"fuzzy_kcat_matching",
"get_standard_kcat", # deprecated; alias of assign_standard_kcat
"kcats_from_brenda",
"kcats_from_dlkcat",
"kcats_from_okp",
"load_kcat_source",
"merge_dlkcat_and_fuzzy_kcats", # deprecated; use merge_kcats
"merge_kcats",
"normalize_source",
"read_kcat_source",
"read_dlkcat_output",
"remove_standard_kcat",
"run_dlkcat",
"save_kcat_source",
"select_kcat_value", # deprecated; alias of apply_kcat_list
"submit_open_kinetics_predictor",
"update_kcat_sources",
"write_dlkcat_input",
]
7 changes: 6 additions & 1 deletion src/geckopy/gather_kcats/fuzzy_kcat_matching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from ._organism import filter_by_organism, resolve_organism_index

if TYPE_CHECKING:
from ...adapter import ModelAdapter
from ...databases import BrendaData, PhylDist
from ...ec_model.ec_model import EcModel

Expand Down Expand Up @@ -81,6 +82,7 @@ def fuzzy_kcat_matching(
ec_rxns: Optional[Iterable[str]] = None,
force_wildcard_level: int = 0,
aggregate: Optional[str] = None,
adapter: Optional["ModelAdapter"] = None,
) -> pd.DataFrame:
"""Match each ec reaction to BRENDA kcat data with progressive
relaxation of EC, substrate, and organism specificity.
Expand Down Expand Up @@ -161,6 +163,9 @@ def fuzzy_kcat_matching(
across whichever rows survive the EC + organism + substrate
gates. See ``docs/kcat_aggregation.md`` for the empirical
rationale.
adapter
Model adapter for ``org_name`` and the aggregation default
(default: ``model.adapter``).

Returns
-------
Expand All @@ -182,7 +187,7 @@ def fuzzy_kcat_matching(
"""
from ...adapter import resolve_adapter
adapter = resolve_adapter(
model,
model, adapter,
purpose="fuzzy_kcat_matching reads params.org_name from the adapter",
)
if force_wildcard_level < 0:
Expand Down
Loading
Loading