Skip to content

Release 3.0.0b2 - #194

Merged
edkerk merged 114 commits into
mainfrom
develop
Nov 1, 2026
Merged

edkerk merged 114 commits into
mainfrom
develop

Conversation

@edkerk

@edkerk edkerk commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

Release 3.0.0b2: merges develop into main. pyproject.toml is at 3.0.0b2, and the 3.0.0b2 section of CHANGELOG.md lists the changes since v3.0.0b1.

After merging, tag v3.0.0b2 on the merge commit. The release workflow builds the package, checks the version against the tag, publishes to PyPI and creates a GitHub pre-release.

README.md and docs/index.md described 3.0.0b1 as work in progress, written before the tag
existed; both now point at the actual release notes and say how to install the beta
(pip install --pre) instead of only the git-checkout route. docs/installation.md gained the
pip install instructions it never had.
…ecko-parity (#153)

Compartment assignment and per-reaction confidence tracking are still in
development and not at parity with MATLAB RAVEN. Published as documentation they
read as statements about what the package does today, which is not what they
are. They now sit in raven-gecko-parity/docs/localization/ alongside the rest of
the cross-implementation record, and come back beside a guide page when either
feature settles.

Fourteen documents from `docs/studies/` move, which empties the folder, and two
from `docs/reference/`: `transport_evidence_scoring.md`, whose own scope is both
RAVEN and raven-toolbox, and `multi_localization_design.md`.

`docs/guide/localization.md` stays -- the feature ships, and a user needs to know
how to call it -- with its links repointed. `reference/index.md` says where the
design documents went and why; `migration.md`, `matlab_raven_backports.md` and
`todo.md` follow the same links.

See CompMetEng/raven-gecko-parity#108.
The move in #153 repointed the documentation tree but not the docstrings,
scripts, test notes and data README that name the same files, so twenty-three
references still resolve to docs/studies/ and two reference/ pages that are no
longer in this repository.

- `localization/transport_evidence.py`, `localization/transporter_tables.py`: a
  `:doc:` role targeting `/reference/transport_evidence_scoring`, a page that no
  longer exists here.
- `confidence.py`, `localization/certify.py`, `localization/curation.py`,
  `localization/scores.py`, `localization/triage.py`: docstrings naming
  `docs/studies/*.md` paths.
- `localization/certify.py`: `_enrich_multilocalization` cited
  `docs/studies/biological_validation.md`, a filename this repository never had;
  the design it describes is `multi_localization_design.md`.
- `scripts/`: `benchmark_carvefungi_milp.py`, `run_carvefungi_cplex.py` and
  `measure_confidence_facets.py` name the documents they produce or explain, and
  `compare_predictlocalization.py`'s usage example writes its output over one.
- `tests/parity/README.md`, `tests/parity/test_genome_scale.py`: both describe
  the validation runs as living in `docs/studies/`.
- `data/deeploc/README.md`: the four DeepLoc benchmarks each committed dataset
  feeds.
- `docs/reference/migration.md`: the `predictLocalization` row linked the
  head-to-head study on raven-docs, which removed it in the same round.
* Add a contributing guide covering the artefact build pipeline

The KEGG dump download, parse and HMM-library build, and the binary bundle conventions, were documented in raven-gecko-parity even though this repository implements all of them.

* Point the raven-docs links at the repository, not an invented site URL
Benchmark provenance for the localization studies (DeepLoc 2.1 submission FASTA and
result CSVs), not runtime data - shipping it in the sdist served no purpose. The
scripts that read it now expect it fetched from raven-gecko-parity/data/deeploc/.
The tree documented only the Python half, thinly, of material raven-docs now covers for both toolboxes with executed examples. Its four development records moved to raven-gecko-parity, and the Sphinx build job had nothing left to build.
parse_hpa/parse_hpa_rna/scoreModel-equivalent adapters are contextualization
inputs for ftinit, not general-purpose omics utilities; omics/ held nothing
else and is now gone. Breaking: raven_toolbox.omics.* moves to
raven_toolbox.init.*.

- `src/raven_toolbox/omics/hpa.py`: moved to `src/raven_toolbox/init/hpa.py`,
  re-exported from `init/__init__.py` alongside the rest of the ftINIT API.
- `tests/test_omics_hpa.py`: renamed to `tests/test_init_hpa.py`, matching the
  `test_init_*.py` naming used by the rest of the package.
- `docs/guide/omics.md`: folded into `docs/guide/context_specific.md` as an
  HPA scoring adapters section; `docs/reference/api/omics.md` folded into
  `docs/reference/api/init.md` (already imported-members, so the HPA
  exports show up automatically).
- `docs/guide/quickstart.md`, `docs/reference/migration.md`: import path and
  migration-map links updated.
An adversarial pass over both modules for speed turned up several costs paid
unconditionally that were either avoidable or not worth their price.

- `ftinit` -> `fill_tasks`: `fill_tasks` copied `model` again even though
  `ftinit` had already made its own disposable copy immediately before
  calling it, so gap-filling paid for two full copies of a genome-scale,
  Gurobi-backed model instead of one. `fill_tasks` now takes an opt-in
  `mutate_in_place` flag; `ftinit` is the only caller that sets it.
- `_resolve_ties`/`_resolve_ties_fill`: a `resolve_ties` tie-break phase
  inherited the primary solve's full time budget, even though a phase that
  times out is adopted no differently from one that converges early - so an
  unproven phase burned up to the entire primary budget for no different
  outcome. Both now cap each phase to a fraction of that budget
  (`_TIE_BREAK_TIME_FRACTION`, not independently calibrated yet).
- `fill_tasks`: `task_name_maps` and every candidate's cost were rebuilt from
  a full sweep of the model on every task, including tasks that changed
  nothing since the previous iteration.

Verified against the full non-slow test suite (1024 passed) plus ruff and
mypy on both files.
)

`run_ftinit` hardcoded `FeasibilityTol`, `OptimalityTol` and `IntFeasTol` to 1e-9 and `Presolve` to 2 (RAVEN parity), so their effect on runtime and on the extracted model could not be measured without editing the source.

- `run_ftinit`/`ftinit`: the solver tolerances and presolve level had no way to be varied; they are now `feas_tol`, `opt_tol`, `int_feas_tol` and `presolve`, defaulting to the previous values so behavior is unchanged.
- `test_init_ftinit`: adds a test that the values reach Gurobi's `Params` and that the defaults are RAVEN's.
…olves (#161)

merge_linear and merge_compartments built their new model with cobra's global
default solver instead of the source model's, so a caller that set the default
to GLPK got a GLPK ftINIT MILP from a Gurobi template. run_ftinit now warns when
it solves with GLPK instead of skipping the Gurobi parameters silently, and pins
optlang's "auto" presolve so a time-limited solve is not repeated from scratch.
The METS sheet has its own InChI column, so an inchi annotation was listed twice.
MIRIAM now leaves it out, as it already did for SMILES, and the InChI column falls
back to annotation['inchi'] when a metabolite has no inchis note.
writeYAMLmodel.m (RAVEN 3) and write_yaml_model write every number as a float,
so charges arrive as -1.0. cobra's SBML writer passes the charge unchanged to
libsbml's setCharge, which does not keep a float, so every charge was lost when
such a model was written to SBML.
read_yaml_model keeps a reaction's subsystem as a list, as the YAML format
writes it, but cobra's save_matlab_model only handles a string and fails on a
list. export_for_git now joins them with subsystem_to_str for the .mat file, as
export_to_excel already does, on a copy of the model.
…omology, drop duplicate genes (#166)

get_model_from_homology gains keep_gene_free and preserve_notes, so a curated template can
seed a model for another organism without losing its reactions that have no GPR or the
references and confidence scores of the rest. Rewritten GPRs no longer repeat a gene when
several template genes map to the same ortholog, and the draft no longer carries the
template's version and date.
…n export_for_git (#167)

* feat: set_model_metadata / get_model_metadata, and version and date in export_for_git

The YAML writer reads the version from notes['version'] and the date from
notes['metaData']['date'], so stamping a release meant setting both by hand. One function
sets them, and export_for_git can stamp the exported copy without changing the model.

* fix: write a version kept only in metaData, and validate the metadata date

write_yaml_model ignored a version stored only in notes['metaData'], although
get_model_metadata and the reader both treat it as the model's version. set_model_metadata
now accepts only YYYY-MM-DD strings, dates and datetimes (reduced to the date), so a
free-text or timestamped date cannot reach the YAML.
* fix: write RAVEN-format .mat and RAVEN-readable SBML

The .mat export delegated to cobra's save_matlab_model, which writes a
COBRA Toolbox structure: COBRA's field names (description/modelName,
rxnECNumbers, geneNames), no slot for version, date, annotation,
metFrom/rxnFrom, rxnNotes/rxnReferences/rxnConfidenceScores, inchis or
the *Miriams structs, and rev as a platform-width integer. An integer rev
makes RAVEN's mergeLinear fail on `S(...).' > 0 | rev`, which MATLAB
rejects as a sparse integer operation.

io.mat now writes and reads the RAVEN structure directly. The layout
follows what RAVEN's own exporter produces, taken from the released
yeast-GEM and Human-GEM files: writing yeast-GEM through it reproduces
RAVEN's own yeast-GEM.mat field for field, with the same classes, shapes,
sparsity and nested subSystems/MIRIAM structures. metComps is the one
integer column, so a reader that turns the 1-based index into a position
gets one; it is signed, so a metabolite with no compartment goes negative
on that subtraction rather than wrapping round.

The SBML export lost five fields to RAVEN's importModel. Only subSystems
was actually absent, because cobra writes the groups package from
model.groups and a model read from YAML carries its subsystems on the
reaction; rxnNotes, rxnReferences and rxnConfidenceScores were present
under cobra's key names rather than the labels importModel parses, and
the model note was not written as its own notes entry. _for_sbml shapes a
copy for the SBML write, leaving the caller's model alone.

This supersedes #165, which joined subsystems into a string for cobra's
writer; the RAVEN writer keeps them as a cell per reaction.

* feat: read a COBRA Toolbox .mat as well as a RAVEN one

read_matlab_model assumed a RAVEN structure, so a COBRA one came back
half-read and silently: the skeleton (stoichiometry, bounds, GPRs,
subsystems, compartments, charges, formulas) was right, but everything
COBRA keeps under its own names was dropped -- the identifier
(description/modelName), EC codes (rxnECNumbers), gene names (geneNames)
and every cross-reference field.

A COBRA structure is now recognised and handed to cobra's own reader,
which already knows all of those mappings, rather than repeating the
table here; the result has its annotations normalised to lists so a model
read from either layout looks the same as one from read_yaml_model. A
struct that is neither raises instead of returning something plausible
but wrong.

Human-GEM 2.0.1 and 2.1.0 shipped a COBRA-format .mat, so this is the way
back from those releases: reading one and writing it out again produces
the RAVEN file they should have had.

* fix: type annotations in io.mat

Three loops over a float column reused the variable the string loops
above bound, the subsystem accumulator had no annotation, and the
metadata dict holds the default bounds as floats beside its string
fields.
cobra writes a notes entry as "<p>key: value</p>" with str(value), and
read_yaml_model parks the parsed metaData block in notes["metaData"] as a
dict, so the whole thing was published as its Python repr:

  <p>metaData: {&apos;id&apos;: &apos;HumanGEM&apos;, &apos;name&apos;: ...

Human-GEM 2.1.0 shipped that. It is unparseable, it duplicates data the
file already carries properly as the model id, name and annotation, and
on Python 3.14 the repr gained an "OrderedDict(...)" wrapper, so it was
about to get worse.

Only string-valued notes are written now, which covers this and any
future non-string rather than that one key. The version and date reached
the file only inside that repr, so they are added as their own entries
alongside the note instead.
* feat: read SuppOrth consensus orthologs as homology input

hits_from_supporth reads suppOrth.tsv into the homology hits table, with
filters on predictor support, required predictors and SuppOrth's own
alignment values, and protein-to-gene mapping. run_supporth runs the
suppOrth command and reads its output. get_model_from_homology warns
when review_identity is used on SuppOrth hits.

Closes #170

* fix: handle shared protein ids and tool lists in run_supporth

Proteomes that share ids are passed to SuppOrth with a per-species
prefix that is removed from the hits. A comma-separated tools string
counts each tool, a single model id is not split into characters,
labels with surrounding spaces and pickle gene maps are rejected, and
support_lv above the number of predictors warns. Predictor labels in
supported_by follow SuppOrth's F, S, B, O order.

* fix: reject gene ids cobra splits, and keep run_supporth's log

cobra's GPR parser splits gene ids at whitespace, parentheses, "|" and
"&", so UniProt headers such as sp|P33284|HXK_KLULA became three genes
in the draft model. get_model_from_homology now raises when a mapped
gene id of the new organism contains one of these characters, and
points to protein_to_gene.

run_supporth writes SuppOrth's console output to
outdir/run_supporth.log, and its error keeps FAILED and error lines
that the consensus summary pushes out of the last 20 lines.

* fix: keep tied best hits and an exclusive e-value cutoff, as RAVEN

With strictness 3, get_model_from_homology kept one best hit per gene,
chosen by row order when bitscores tie. RAVEN's getModelFromHomology
keeps every hit tied for the best bitscore, and so does this now. The
e-value filter accepts hits below max_evalue, not at it, also as in
RAVEN. The strictness 1 description no longer says best match: it
requires an accepted hit in both directions.

* fix: parse hit values exactly and filter only_genes_in_models on the template gene

pandas' default float parser reads an e-value printed as 1e-30 as
9.999999999999999e-31, so a hit at the cutoff passed max_evalue=1e-30
where RAVEN rejects it. BLAST/DIAMOND output, saved hit tables and
suppOrth.tsv are now read with round_trip precision.

only_genes_in_models kept a hit when either gene was in a template, so
a new-organism gene sharing an id with a template gene kept hits to
genes no template has, and with strictness 3 such a hit could displace
the real best hit. As in RAVEN, the template-side gene decides.

* fix: reject an emptied search direction and miscased organism ids in get_model_from_homology

With only_genes_in_models, a search direction between model_for and a
template with no hit left after the cutoffs and the filter raises an
error, as in RAVEN. Organism ids that match model_for or a template id
only when case is ignored raise an error instead of being read as
another organism.
raven-data moved to CompMetEng/toolbox-assets. The baked registries, the
manifest, the publishing and registry scripts and the docs point there;
publish_to_raven_data.py is renamed publish_to_toolbox_assets.py.
RAVEN moved its source under src/ and its tutorial models to
tutorials/data/; the parity fixtures skipped every check against such a
checkout.
* fix: read the SBO term from YAML as a string

read_yaml_model wrapped every annotation value in a list, so
annotation["sbo"] came back as ['SBO:...'] while add_sbo_terms and
cobra's SBML reader give a bare string. sbo is single-valued and is now
kept as a string, also when the file spells it as a one-item list.

* fix: store batch-curated notes under the keys the YAML layer uses

batch_curate kept metNotes, rxnNotes, rxnReferences and
rxnConfidenceScores under their table names in .notes, and the InChI in
annotation["inchi"]. read_yaml_model and the YAML/MAT writers use note,
references, confidence_score and inchis, so write_yaml_model emitted a
nested mapping that MATLAB read back as "metNotes: ...", and a model read
from YAML ended up with two different InChIs. The values now go under
those keys, the confidence score as a number, and an InChI already held
as an annotation is updated too.

* fix: raise in scale_biomass when the balancing component cannot absorb the change

With balance_out, scale_biomass rescaled the balancing component so the
total is 1 g/gDW without checking that its new fraction stays positive. A
negative factor turned the pseudoreaction's substrates into products and
the model was changed without an error; a balancing component with
fraction 0 was skipped silently. Both now raise before the model is
changed, and the docstring says the total becomes 1 g/gDW rather than
staying there.

* fix: stop fill_gaps_kumar_milp reporting a repair that does not restore growth

The indicator coupling uses big-M 1000, so with the solver's default
integrality tolerance (about 1e-5) a "closed" candidate could carry about
0.01 flux, enough for the default 10 % growth floor. On smallYeast this
returned "optimal" with nothing added for the RPI, DAR, CIT, ACO, IDP1,
CO2TRANS and GROWTH removals on GLPK. The MILP now runs with an
integrality tolerance of 1e-9, as RAVEN's optimizeProb does, and the
rounded selection is checked on the filled model: when it does not reach
min_growth the result has exit_status "not_restored".

* docs: describe the reporter_metabolites background correction and RAVEN parity as implemented

The docstring credited the parity to replacing RAVEN's Monte-Carlo background, which RAVEN no longer uses, said RAVEN sorts by p-value and reports both tails, and cited an IMPROVEMENTS entry that does not exist. It now states the population standard deviation, the one-sided p-value and the z-score sort, and the constant factor a RAVEN version with the sample standard deviation differs by.

* docs: bring the sampling module and random_sampling docstrings up to date

The module docstring presented the module as random-objective sampling only, said RAVEN has no seed and hard-codes two objectives, and cited an IMPROVEMENTS entry that does not exist. It now describes the method dispatch and the actual differences from RAVEN's random-objective method. The convergence study link points to CompMetEng/raven-docs.

* docs: say which bounds remove_no_flux_reactions tests

The docstrings said it removes reactions that cannot carry flux in any steady state and never one that merely lacks an open boundary. Only model.exchanges are opened; sinks, demands and internal bounds keep their values.

* docs: qualify the RAVEN parity of the check_tasks close_boundaries default

RAVEN's checkTasks leaves the model's exchanges open only on a model without boundary metabolites; prepINITModel calls closeModel first, which corresponds to close_boundaries=True.

* docs: state that blocked reactions are blocked under the tested bounds

connect_blocked_reactions and the remove_no_flux_reactions summary described blocked reactions without saying they are found under the model's current (or exchange-opened) bounds.

* docs: say that find_leak_metabolite needs a closed model and when gap_report raises

find_leak_metabolite uses the model's boundary reactions as they are, so the caller has to close them, and an infeasible search does not show the model is leak-free. gap_report raises Infeasible when the model, or its closed copy for the mass-balancing section, is infeasible.

* docs: fix the README documentation claims and links, and the CONTRIBUTING docstring convention

The README linked the MkDocs URL of the RAVEN 3 comparison page, which gives 404, and said every example on raven-docs is executed on each commit. CONTRIBUTING said all docstrings are NumPy style because the site's generator expects it; most are prose and the generator does not parse sections. raven-docs links point to CompMetEng.

* docs: point the raven-docs study links in ftinit and homology to CompMetEng

* docs: correct the Kumar et al. citation and describe the reversal candidates in fill_gaps_kumar_milp

The method is from BMC Bioinformatics 8:212. The module docstring now says that every irreversible draft reaction, exchanges and maintenance drains included, is a reversal candidate.

* fix: stop gap_report on infeasible models with a pointer to forced fluxes

gap_report raised a bare cobra Infeasible partway through when the model
was infeasible under its own bounds or once its boundary was closed. It
now raises a ValueError up front naming the reactions with a forced flux
and asking for ATP maintenance and similar bounds to be set to zero.

* fill_gaps_kumar_milp adds template reactions only

Draft reactions keep their bounds; the MILP minimises the number of added
template reactions. reversed_reactions and the weights argument are removed.

* fill_gaps_kumar_milp: report an infeasible MILP on Gurobi instead of crashing

* Rename fill_gaps_kumar_milp to fill_gaps_milp

Without directionality reversal the function is no longer Kumar's GapFill;
the name now matches RAVEN's gapFillMILP. KumarGapFillResult becomes
MILPGapFillResult and the module gapfilling.kumar_milp becomes gapfilling.milp.
* docs: describe the current code, not its development history

Docstrings, comments and maintainer scripts state what the code does and
which RAVEN 3 function it matches, without develop3 branch names or
references to SysBioChalmers/RAVEN pull requests. The artefact publishing
link points to CompMetEng/raven-gecko-parity.

* scale_biomass: optional target_total for the balanced biomass total

With balance_out, the balancing component brings the components to
target_total g/gDW (default 1), matching RAVEN's scaleBiomassFraction
targetTotal argument.

* docs: gap_report closes the boundary as RAVEN's gapReport does
* fix: quote YAML 1.1 booleans such as NO in the YAML writer

A YAML 1.1 loader such as PyYAML read an unquoted `name: NO` (nitric
oxide) as False. y, n, yes, no, on and off are now quoted in any case,
as true and false already were.

#178

* fix: write the gene name as fbc:label in export_for_git SBML

cobra's writer puts the gene product id in fbc:label, and RAVEN's
importModel reads geneShortNames from fbc:label before fbc:name, so it
read the gene ids as gene short names. Genes without a name keep the id
as label.

#178

* fix: raise on duplicate and conflicting task inputs and outputs

The outputs of a task were applied after its inputs and overwrote their
bounds, so a metabolite that is both IN and OUT with nonzero lower bounds
became "produce exactly LB", and a metabolite listed twice on one side
kept its last line. Both now raise a ValueError with the messages of
RAVEN's checkTasks.

#178

* feat: report reactions cut to one of tied isozymes in gene pruning

When all alternatives of an OR rule score negative and several share the
best score, remove_low_score_genes keeps the first, so the result depends
on the order of the rule. A list passed as tied_reactions receives the
ids of these reactions.

#178

* ci: upgrade pip with python -m pip

pip 26.2.2 refuses to replace itself on Windows when run as plain pip and exits 1, so every Windows job failed before running any test.
…em of transport reactions (#180)

* fix: keep everything of every model in merge_models; name the subsystem of transport reactions

merge_models added only the metabolites and genes that a reaction uses, dropped the objective, gave a compartment name to the last model that named it, and ordered new metabolites and genes by the reactions that reached them. RAVEN's mergeModels keeps all metabolites, genes and compartments of every model, concatenates the objective, keeps the first model's compartment name, and appends new items in the order of their source model. Confirmed on MATLAB (RAVEN 79a7d8c): an unused metabolite and gene survive and both objectives are kept.

add_transport_reactions left the subsystem empty. RAVEN's addTransport sets "Transport between <from> and <to>" (reversible) or "Transport from <from> to <to>" (irreversible), using the compartment ids; confirmed on MATLAB for c and m.

* fix: set the hash seed explicitly in the merge_models order test

The test passed -I, which makes Python ignore PYTHONHASHSEED, and replaced the whole environment, which stops Python from starting on Windows (no SYSTEMROOT). It now keeps the environment and sets the seed.

* ci: upgrade pip with python -m pip

pip 26.2.2 refuses to replace itself on Windows when run as plain pip and exits 1, so every Windows job failed before running any test.
* Align raven-toolbox with RAVEN on task lists, GPR parsing and gene-prune ties

parse_task_list: an empty CHANGED LB/UB now leaves the reaction bound as it is
(None) instead of defaulting to -1000/1000, and .xls gets a clear error.
check_tasks warns when a name[comp] matches several metabolites.
is_dnf raises ValueError on a malformed rule instead of reporting True.
write_yaml_model warns on metabolites with an empty name.
Tests for each, plus ties in remove_low_score_genes.

* ci: upgrade pip with python -m pip

pip 26.2.2 refuses to replace itself on Windows when run as plain pip and exits 1, so every Windows job failed before running any test.
cobra's flux_variability_analysis and find_blocked_reactions start a worker pool by default. With Python 3.14 (forkserver start method) the pool did not return in gap_report on a cluster node: the main thread waited in Pool.join. The gap-filling modules and the localization certificate now pass processes=1, as the certificate's FVA already did.
…pendencies

The default formats are now yml, xml, mat, xlsx and txt, as in RAVEN. The txt table is headed 'Rxn id', writes bounds and objective coefficients with the digits needed to read them back (RAVEN's exportForGit does the same) and pads no field. dependencies.txt lists python, cobra, libSBML, ruamel.yaml, scipy, openpyxl and raven_toolbox in a fixed order, then the model's version and date; it holds no time of export.
edkerk added 29 commits October 24, 2026 07:46
…, true or false and an rxnIdx cell a whole number of at least 1; a coefficient row without content is ignored; ftinit removes every metabolite without a reaction from the extracted model
…s, tables are validated before the model is changed, a coefficients table without its coefficient column names the missing columns; tests
…er, validate every table before the model is touched, match reaction tables on the numeric index, sum repeated coefficients; ftinit drops genes without a reaction
…uched; row-number wording of the missing rxnIdx error follows RAVEN
… one reaction and a repeated new gene are checked before the model is touched; the rule of a new reaction names only known genes; core headers are matched as in RAVEN; the header row is read like readCurationTable
…that cannot be performed is removed by its position; compare_models fills the task matrix by position
… capped at big_m (default 1000)

The direction and off constraints of a scored reaction use max(min(ub, big_m),
min(-lb, big_m), force_on), with an infinite bound counted as big_m, as RAVEN's
ftINITInternalAlg does. big_m is the cap on that capacity.
…, so the essential set does not depend on the hash seed
…ing it infeasible

A knock-out solve starts from the basis of the previous one. From such a
start Gurobi reported two feasible Human-GEM knock-outs as infeasible
(MAR04410, MAR03802), which put them in the essential set. A status that
is not feasible is now checked again from a cold start, as RAVEN's
solveLP does after a failed hot-started solve, and only a knock-out that
is infeasible on both solves counts as essential. Feasible statuses
follow RAVEN's checkSolution (optimal, suboptimal, unbounded).
fix: align raven-toolbox with RAVEN (parity review)
fix: write a single ec-code or smiles annotation as a list
…eof follows ecFSEOF (#185)

* fix: read_yaml_model reads empty enzymes omaps and doubled-quote scalars

An ec reaction without enzymes is written as '- enzymes: !!omap' with no
content; the reader now gives it no enzymes, as RAVEN's readYAMLmodel does.
A scalar written as ""text"" is repaired line by line only after the strict
parse fails, and reads as "text".

* fix: read_yaml_model keeps an unescaped quote inside a double-quoted value

The fallback after a failed strict parse now repairs any double-quoted
value with an unescaped interior quote, not only one that starts and
ends with a doubled quote. The outer pair is dropped and \\ and \" are
unescaped, as readYAMLmodel does.

* fix: fseof holds the objective at 0.999999 of its optimum in the pFBA step

RAVEN's solveLP minimises the flux sum with the objective fixed at
0.999999 of the optimum it just found. fseof demanded the full value,
which the HiGHS solver of the scipy interface rejects at the top level of
ecYeastGEM because its optimum is accurate only to the feasibility
tolerance. All enforced levels are now returned on scipy, as on Gurobi
and GLPK.

* fix: read_yaml_model keeps an apostrophe inside a single-quoted value

The fallback after a failed strict parse now also repairs a single-quoted
value with an apostrophe that no second apostrophe pairs with, such as
'it's'. The outer pair is dropped and each '' is read as ', as
readYAMLmodel does.

* fix: fseof takes its top level from the pFBA maximum, sorts stably and can fix the target

The top of the scan is max_fraction of the target flux in the pFBA solution
that maximises the target, as RAVEN's FSEOF and GECKO's ecFSEOF do. Targets
of equal score keep model order. The new equality option fixes the target
flux at each level, as ecFSEOF does.

* fix: sort the fseof imports and name the exception the invalid-YAML test expects
* ftinit: resolve_ties takes each step's fluxes from a least-total-flux solution

After the id-rank phase, a further phase holds the on/off state and minimises the summed absolute flux, so the direction and magnitude at which the next step forces a reaction do not depend on the solver's vertex.

* ftinit: the resolve_ties minimal-flux phase stops at a 1e-6 relative or absolute gap and has the full time_limit

* ftinit: resolve_ties fixes equal-total-flux directions by id rank and ranks a merged reaction by its smallest original id

* ftinit: unpack the minimal-flux phase result before the direction phase

* ftinit: the resolve_ties direction phase starts from the minimal-flux solution

* merge_compartments: keep the id of the copy in the merged compartment or the one that sorts first

The merged metabolite took the first member's id with its compartment
suffix rewritten (ACCOA_c became ACCOA_s). It now keeps the id of the copy
already in the merged compartment, if any, otherwise the id that sorts
first, as RAVEN's mergeCompartments does.

* merge_linear: number groups by their first reaction

Group numbers followed the merge order. They are now 1, 2, ... in the
order of the first reaction of each group in the original model, as
RAVEN's mergeLinear numbers them.

* gpr_to_dnf and is_dnf read a rule string with parse_gr_rule, the grammar of RAVEN's parseGrRule, and raise ValueError for a malformed rule

* analyse_topology: default seeds and targets are those of RAVEN's gapFillTopological

* run_blast, run_diamond: threads default to every CPU the process may use, as in getBlast and getDiamond

* merge_compartments: keep the annotation of the kept copy; drop_single_metabolite_reactions defaults to False

The merged metabolite kept the id, name, formula, charge and notes of the
chosen copy and dropped its annotation. It now keeps the annotation too, as
RAVEN's mergeCompartments keeps every field of the kept row.
drop_single_metabolite_reactions defaults to False, the default of RAVEN's
deleteRxnsWithOneMet.

* localization: the default metabolite key of a merged model matches the universal copy

merge_compartments keeps the id of one copy, so a merged ACCOA_c sits in s and
its own-compartment suffix rule left the key ACCOA_c. A universal ACCOA_m has
the key ACCOA, so apply_assignment added a second copy of the metabolite in m.
assign_compartments, apply_assignment, relocate_reactions and
curation_priority now also strip a suffix that names a compartment of the
model, the universal model, the scores or the placements.

* parse_gr_rule: whitespace is what MATLAB's isspace counts

The tokeniser split words at Python's Unicode whitespace, so a no-break space
(U+00A0), U+0085, U+2007 and U+202F separated words, while RAVEN's
parseGrRule, which uses isspace, keeps them in the word and refuses
G1<U+00A0>and G2. The whitespace class is now the list of code points for
which isspace(char(0:65535)) is true in MATLAB R2024b.

* batch_curate: store the gene rule as parse_gr_rule reads it

The rule was checked with parse_gr_rule, for which G1&G2 is one gene id, and
then stored as a string that cobra reads as G1 and G2. The reaction now gets
the parsed rule, so its genes are the ones that were checked, as in RAVEN's
curateModelFromTables.

* run_hmmsearch, get_kegg_model_from_sequences: threads default to every CPU the process may use

The default was the machine's CPU count minus one. It is now the number of
CPUs the process may run on, as in run_blast and run_diamond and in RAVEN's
getKEGGModelForOrganism, which passes feature('numcores')'s assigned count
to hmmsearch.

* remove_genes: constrain blocked reactions by default and remove unused genes, as removeGenes

* tests: merge_linear marks the reaction flipped in a later pass as reversed

* read_yaml_model: notes that write_yaml_model writes as a notes mapping go back into notes

write_yaml_model writes the notes without a RAVEN field as a notes mapping,
which read_yaml_model nested under notes["note"]. They are now spread back
into notes, with the free-text note under "note".

* localization: the default metabolite key is the base id merge_compartments stores

The default key guessed a compartment suffix from the compartments each
function saw, so assign_compartments and apply_assignment could key one
metabolite differently (I and I_g), and distinct species such as X_c and
X_m in s, or vitamin_a and vitamin_c, got one key. merge_compartments now
stores the base id of each merged metabolite in notes["merged_base_id"]
(the own id when two would coincide), and the default key is that stored id,
otherwise the id without the suffix of the metabolite's own compartment.
assign_compartments, apply_assignment, relocate_reactions and
curation_priority raise ValueError when two metabolites in one compartment
have the same key.

* read_yaml_model: read gene rules with parse_gr_rule

cobra's model_from_dict read the rule G1&G2 or G3, written by write_yaml_model
after batch_curate, as the genes G1, G2 and G3. The reader now gives each
reaction the rule as parse_gr_rule reads it, so G1&G2 stays one gene; a rule
that parse_gr_rule refuses is read by cobra as before. The batch_curate help
states that cobra's JSON reader still splits such an id.

* run_supporth, build_ko_hmm, build_hmm_library: threads default to every CPU the process may use

As run_blast and run_hmmsearch, and as RAVEN's runSuppOrth, which passes the
cores assigned to MATLAB (was: the machine's CPU count minus one).

* tests: hmmsearch thread default compares the --cpu value

The test patched a helper that only exists once the default is in place, so
without it the test stopped with an AttributeError. It now fixes the CPUs
the process may use and checks the --cpu value, also for
get_kegg_model_from_sequences without threads.

* Keep RAVEN's reading of a gene rule when a function copies or builds it

utils.gpr.set_gene_rule copies a GPR object, or reads a rule string with
parse_gr_rule, so G1&G2 stays one gene id. merge_compartments,
copy_to_compartment, apply_assignment, convert_to_irreversible,
merge_models, add_reactions_from_model, expand_model,
change_gene_reaction_rules, add_reactions_from_equations, fill_tasks,
remove_low_score_genes, the homology and KEGG model builders and
read_matlab_model set rules through it instead of assigning the string
to gene_reaction_rule, which cobra read with its own grammar.

* remove_genes: state that nothing changes when no requested gene is in the model

* benchmark_replicate_yeast_gem: drop_single_metabolite_reactions defaults to False

* tests: ftinit ranks a merged reaction by the smallest original id of its group

* tests: the paths of the rank-key test model are reversible, as in RAVEN's test

* run_ftinit: resolve_ties warns when the minimal-flux or the direction phase finds no solution

* tests: resolve_ties warns when phase 2c or 2d finds no solution

* ftinit: resolve_ties orients a merged reaction as its smallest member and skips the flux phases in the last step

* tests: resolve_ties follows orientation, pin_fluxes skips the flux phases, ftinit passes both

* merge_compartments: docstring states where notes["merged_base_id"] survives and what happens when it is lost

* run_ftinit: docstring and comments of pin_fluxes name the phases it skips

* ftinit: resolve_ties keeps the orientation of each merged reaction as written

_min_backward_flux weights the backward flux by the rank key only and a turned-on reaction with zero flux is forced forward in the next step. run_ftinit takes the private keyword _pin_fluxes, which ftinit sets false in its last step. The tests check that run_ftinit skips phases 2c and 2d with _pin_fluxes false and that ftinit sets it false in the last step only.

* CHANGELOG: warnings of the resolve_ties minimal-flux and direction phases
* docs: rewrite the docstrings under the documentation prose rules

The docstrings state behaviour in impersonal, present-tense sentences with British spelling, without emphasis, history or slash alternatives. Code is unchanged.

Statements that differed from the code are corrected, among them:
- `find_good_reactions` keeps a reaction when the larger of |minimum| and |maximum| exceeds `flux_tol`.
- `sum_biomass` logs no warning for a missing pseudoreaction and raises `ValueError` for an empty formula.
- `diff_models` compares gene-reaction rules as logic; `compare_models` suffixes repeated ids with `__<n>`.
- `set_gene_rule` is not the only place that assigns a rule; `EcData.validate` is not called by the loader.
- `constrain_reversible_reactions` sets `(0, -lower_bound)` and skips reactions with an infinite FVA range.
- `fseof` classifies by the thresholds of `FSEOF` and `flux_eps` only gates the theoretical-maximum check.
- `ftinit` essential forcing is `min(0.99 * flux, force_on)`; `mip_gap` and `mip_gap_abs` act on a step without a run schedule; the determinism figures are those of the rerun study.
- `fast_lp` matches template metabolites by `name[compartment]`; `reconstruction` docstrings state the MAFFT flags, the `-Z` condition and the E-value floor.
- `batch_curate` matches the reaction tables on index and name and raises for a repeated index, as `curateModelFromTables` does.

* docs: changelog entry for the documentation rewrite
…y oracles (#189)

* docs: parameter entries for the public functions, corrected comments, `set_exchange_bounds` annotation, `--big-m` in the sweep script

- Every public function exported through `__all__` documents all its parameters (117 functions, 565 parameters), with `Returns` and `Raises` sections where the code returns a structure or raises.
- Comments and the `_NON_DNF_REASON` string state the current behaviour: the `prove_abs_gap` measurements of `ftinit`, the case handling of template ids, the flagged form of a rule that is not in disjunctive normal form.
- `set_exchange_bounds` is annotated `list[str | Metabolite]`: it returns the entries of `mets` as supplied.
- `scripts/analyze_init_params.py` takes `--big-m` (default: the `ftinit` default of 1000), and its sweep cache is keyed on `big_m`.

* Fix behaviour differences found in review, add MATLAB parity oracles

fill_tasks restores MIPGap and MIPGapAbs. The essentials cache is JSON and
checkpoint_every below 1 raises. hpa_gene_scores uses the default level scores
for an empty mapping, and rna_gene_scores matches the tissue case-insensitively
and takes the highest TPM over names that differ in case. predict_localization
returns a copy when no gene has a score. assign_compartments derives its
default floor from biomass_reaction. organisms_in_domain raises for an empty
domain. load_condition returns {} for an empty file. reporter_metabolites leaves
a gene without a fold change out of both subsets. constrain_reversible_reactions
catches AttributeError only.

The oracle generator records reporter_metabolites with fold changes and an
empty condition file; the recorded oracles are committed.
…mes (#191)

read_yaml_model reads a missing enzymes key, a content-less enzymes: !!omap and enzymes: !!omap [] as an empty enzyme map.
* docs: describe the effect of thinning on genome-scale sampling without measured figures

* docs: remove measured figures from the docstrings of ftINIT, homology, KEGG and localization
…l coefficient (#192)

* read_yaml_model: negate the objective coefficient of a usage or pool reaction it turns forward

* read_yaml_model: flip usage and pool reactions by the sign of the pool coefficient
…193)

* docs: README with the documentation links and the RAVEN logo for dark mode

* docs: use the Jaccard range of the validation study

* docs: README wording
Version 3.0.0b2, the changelog of the changes since v3.0.0b1 and the GPL-3.0 licence in the package metadata.
@edkerk
edkerk merged commit 5d64a65 into main Nov 1, 2026
10 checks passed
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