Differences in results between functions#
A function of RAVEN and its counterpart in raven-toolbox can differ in the algorithm they choose, in the form of the result or in the value of the result. The sections below describe the differences found by reading the code of both implementations. Where a section says that a result was not checked, no run compared the two implementations.
The list is incomplete, and a function that is not listed can behave differently. Candidates that were not compared include random_sampling, which replaces both sampleACHR and sampleCHRR; remove_duplicate_reactions, which replaces contractModel; and simplify_model, which corresponds to simplifyModel.
Metabolic task checking#
The functions checkTasks and check_tasks implement the same task format and the same boundary semantics. The option close_boundaries=True of check_tasks corresponds to calling checkTasks on closeModel(model). Metabolic tasks describes the options. Whether both functions return identical pass and fail results on a shared input was not checked.
checkTasks starts each task from the original model structure. check_tasks applies each task to one model inside a with model: block and reverts the changes afterwards. It also saves and restores direct edits of metabolite-constraint bounds. The runtime of the two functions was not compared.
The option getEssential of checkTasks has no counterpart option in check_tasks. In RAVEN, checkTasks calls getEssentialRxns on the model constrained by each task, and find_task_essential_reactions ports this step. It is the port of checkTasks with getEssential.
Gap-filling functions#
The function fillGaps of RAVEN selects one of five algorithms with the option algorithm: reference (default), fastLP, swiftLP, gapfillMILP and topological. raven-toolbox has four functions for them, so porting a fillGaps call means choosing one function from the table.
Purpose |
RAVEN algorithm |
raven-toolbox function |
|---|---|---|
Connecting blocked reactions against template models |
|
|
Fast filling of a large candidate set by linear programming (LP) |
|
|
Filling by mixed-integer linear programming (MILP) with the fewest added reactions that restore growth |
|
|
Finding the gaps without filling them |
|
|
The functions differ in three more points.
fillGapswithuseModelConstraints=truefills so that the constraints of the model hold.connect_blocked_reactionshas no such mode, and its documentation points tocobra.flux_analysis.gapfillfor that use. The closest function for a growth target isfill_gaps_milp.fillGapstakes a cell array of template models, but the RAVEN functionsgapFillFastLP,gapFillMILPandgapFillTopologicaluse the first template and ignore the rest. The four raven-toolbox functions accept several templates.fillGapsreturnsnewConnected,cannotConnect,addedRxns,newModelandexitFlag. raven-toolbox returns aGapFillResultfromconnect_blocked_reactions, aFastLPResultfromfill_gaps_fast_lp, aMILPGapFillResultfromfill_gaps_milpand aTopologicalAnalysisResultfromanalyse_topology. The countnewConnectedis not stable across runs (see Gap-filling).
The RAVEN functions canExchange, checkProduction and getAllSubGraphs find gaps without filling them. raven-toolbox merges them into gap_report, the port of gapReport, and has no separate public function for each of them.
Alternative optima of mixed-integer problems#
The extraction with ftINIT, the reference and MILP gap-filling algorithms, and the assignment of compartments (assignCompartments and assign_compartments) solve mixed-integer problems, which can have several optima with the same objective value. The LP algorithms were not measured for this, and the documentation of swiftLP in both implementations calls it stochastic. A solve that stops at its gap or time limit can also end at a different objective value. Two runs can therefore return different reaction sets, and no single exact result is expected.
Four measurements quantify the differences.
Between implementations. The ftINIT models of five cell lines, extracted from the same Human-GEM inputs with RAVEN and raven-toolbox, share Jaccard indices of 0.964 to 0.988 over their reaction sets (see Human-GEM cell-type model validation against RAVEN). The template preparation differed between the two runs.
Between solvers. In the raven-toolbox ftINIT benchmark on Human-GEM with one run per solver, Gurobi extracted 7312 reactions and GLPK 6758 (Jaccard index 0.892). Both main steps of GLPK stopped at the 900 s limit with an unproven incumbent, so the two models differ in objective value as well as in tied optima (see ftINIT on different solvers). RAVEN was not run on several solvers in that study.
Between repeated runs. Two repeated raven-toolbox builds at one seed with the default settings differed by 2 reactions. In each build one run of the final step stopped at its 5 s wall-clock limit, which is the presumed and untested cause (see ftINIT reproducibility with
resolve_tiesandprove_abs_gap). The effects of seed, thread count and time limit were not measured for theftINITof RAVEN.Between thread counts. In the first ftINIT step of raven-toolbox without
resolve_ties, with six seeds, the solutions atThreads1 and 4 for the same seed were 5 to 20 reactions apart. In 5 of the 6 seeds the two runs reached different objective values (see ftINIT reproducibility withresolve_tiesandprove_abs_gap).
Compare such outputs by the overlap of the reaction sets, for example the Jaccard index.
The option resolveTies of the ftINIT of RAVEN and resolve_ties of the ftinit of raven-toolbox are off by default in both. When on, each step keeps the score optimum and picks among the tied solutions in four phases, in this order: the fewest kept removable reactions, the lowest summed ID rank of those, the least total absolute flux and the least backward flux weighted by ID rank. The last step of ftINIT skips the two flux phases. A step that stops at its time limit keeps the answer of the solver.
In raven-toolbox, runs with resolve_ties at the same score optimum returned identical solutions across seeds. The primary solve can end at a different objective value for a different seed, within the gap and time limits, so the final reaction sets can differ. This was not measured for RAVEN. Without resolveTies or resolve_ties, the ftINIT MILP can return any of the tied optima.
Elemental balance results#
The functions getElementalBalance in RAVEN and get_elemental_balance in raven-toolbox check whether each element has the same count on both sides of a reaction. A reaction is unbalanced when the counts were computed and do not match. The result is unknown when the counts cannot be computed. The two functions code these results differently.
The function getElementalBalance returns the numeric field balanceStructure.balanceStatus per reaction: 1 for balanced, 0 for unbalanced, -1 for missing information (no formula, or a reaction without metabolites) and -2 for a formula that cannot be parsed. The function get_elemental_balance returns the strings balanced, unbalanced and unknown. It reports unknown for a missing formula, for a formula that cannot be parsed and for a reaction without metabolites, so the codes -1 and -2 of RAVEN are one result in raven-toolbox.
The two parsers handle some formulas differently, so the results differ for some reactions.
A polymer formula such as
(C5H8)nis counted with n equal to 1 in RAVEN. raven-toolbox reportsunknown.RAVEN uses the field
inchiswhenmetFormulasis absent. raven-toolbox does not.RAVEN reports a charge balance (
chargeStatusandchargeResidual).get_elemental_balancereports elements.
The comparison used the code, and the functions were not run on a shared model.
Parsing of gene-reaction rules#
The parsing of rules matters when a RAVEN user takes a model through cobrapy readers in Python. raven-toolbox parses gene-reaction rules (the field grRules in RAVEN) with utils.parse_gr_rule, which follows the grammar of parseGrRule of RAVEN. The function is used in read_yaml_model, read_matlab_model, batch_curate and the functions that copy or build rules, so G1&G2 is one gene ID there. According to the changelog of raven-toolbox, the JSON reader of cobrapy splits G1&G2 into G1 and G2, and the SBML reader keeps it as one ID. This was not checked against cobrapy itself.