From 5f9b88d6ae8748a1f8f5c67594b326f1994f91c6 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Thu, 15 Oct 2026 13:47:46 +0200 Subject: [PATCH] Read CatRange predictions returned as class ranges 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)'. readOpenKineticsPredictorOutput read that text as NaN and dropped every CatRange prediction. It now 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. Original-commit: https://github.com/SysBioChalmers/GECKO/commit/a8292f9567870bd76ad7fd215fc96cef396ee8ba --- .../readOpenKineticsPredictorOutput.m | 28 +++++++++++++++++- .../submitOpenKineticsPredictor.m | 6 ++-- test/VerificationMatrix.tsv | 1 + test/unit_tests/geckoCoreFunctionTests.m | 29 +++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/gather_kcats/readOpenKineticsPredictorOutput.m b/src/gather_kcats/readOpenKineticsPredictorOutput.m index 64cb3306..63dd6af6 100644 --- a/src/gather_kcats/readOpenKineticsPredictorOutput.m +++ b/src/gather_kcats/readOpenKineticsPredictorOutput.m @@ -6,6 +6,10 @@ % constructs a kcatList structure, that can be used by assignKcatValues() to % populate the ecModel with kcat values. % +% CatRange predicts a class of kcat values, which OKP returns as its range +% (for instance '10 to 100 s^-1'). It is read as the geometric midpoint of +% the range, or half the upper bound for the class starting at 0. +% % The per-entry `Source kcat` column (e.g. 'Prediction from CatPred', % 'BRENDA', 'Sabio-RK', 'UniProt') is preserved in kcatList.kcatSource so % that assignKcatValues() records the actual provenance of each value. @@ -108,7 +112,7 @@ if isnumeric(v) kcats(i) = v; elseif ischar(v) || isstring(v) - kcats(i) = str2double(v); + kcats(i) = kcatFromText(char(v)); end end @@ -255,3 +259,25 @@ end end end + +function kcat = kcatFromText(v) +% A number, or a CatRange class range ' to s^-1' as its +% geometric midpoint (half the upper bound when the class starts at 0). +kcat = str2double(v); +if ~isnan(kcat) + return +end +bounds = regexp(strtrim(v), '^([0-9.eE+-]+)\s+to\s+([0-9.eE+-]+)(\s*s\^-1)?$', 'tokens', 'once'); +if isempty(bounds) + return +end +low = str2double(bounds{1}); +high = str2double(bounds{2}); +if isnan(low) || isnan(high) + return +elseif low > 0 + kcat = sqrt(low * high); +else + kcat = high / 2; +end +end diff --git a/src/gather_kcats/submitOpenKineticsPredictor.m b/src/gather_kcats/submitOpenKineticsPredictor.m index 27b18553..898e2208 100644 --- a/src/gather_kcats/submitOpenKineticsPredictor.m +++ b/src/gather_kcats/submitOpenKineticsPredictor.m @@ -7,8 +7,10 @@ % job id is also stored in data/OKP_job.txt, so fetchOpenKineticsPredictor can % pick it up later. % -% Predictors available via OKP: CataPro, CatPred, DLKcat, EITLEM, KinForm-H, -% KinForm-L, UniKP (see GET /api/v1/methods/). +% Predictors available via OKP: CataPro, CatPred, CatRange, DLKcat, EITLEM, +% KinForm-H, KinForm-L, OmniESI, OmniESI-O2DENet, TurNuP, UniKP (see GET +% /api/v1/methods/). CatRange predicts a class of kcat values; +% readOpenKineticsPredictorOutput uses its geometric midpoint. % % Obtaining an API key: % The OKP API requires a personal Bearer key (looks like 'ak_...'). Keys diff --git a/test/VerificationMatrix.tsv b/test/VerificationMatrix.tsv index fad92a5a..66b8901e 100644 --- a/test/VerificationMatrix.tsv +++ b/test/VerificationMatrix.tsv @@ -15,6 +15,7 @@ gather_kcats/readDLKcatOutput.m tcm0001, tcm0002 - The test doesn't really check gather_kcats/runDLKcat.m tcm0001, tcm0002 - The test doesn't really check the function, only that we get something gather_kcats/assignKcatValues.m tc0011 - we did not test all possible parameterizations here gather_kcats/writeDLKcatInput.m partly by tc0011 +gather_kcats/readOpenKineticsPredictorOutput.m tc0030, tc0069 gather_kcats/reviewKcatAssignment.m Has only been tested by running it manually, not tested by test cases gather_kcats/ecStatsFromBrenda.m Not verified against real BRENDA data in this environment -- see its own TODO comment get_enzyme_data/calculateMW.m Not explicitly tested - The code has existed for a long time diff --git a/test/unit_tests/geckoCoreFunctionTests.m b/test/unit_tests/geckoCoreFunctionTests.m index 1a5ba345..a9b12832 100644 --- a/test/unit_tests/geckoCoreFunctionTests.m +++ b/test/unit_tests/geckoCoreFunctionTests.m @@ -1163,6 +1163,35 @@ function testResolveOkpApiKeyResolutionOrder_tc0029(testCase) end +function testReadOpenKineticsPredictorCatRange_tc0069(testCase) + % CatRange's prediction is a class range ('10 to 100 s^-1'), read as its + % geometric midpoint, or half the upper bound for the class from 0. + geckoPath = findGECKOroot; + adapter = ModelAdapterManager.getAdapter(fullfile(geckoPath,'test','unit_tests','ecTestGEM', 'TestGEMAdapter.m')); + model = getGeckoTestModel(); + ecModel = makeEcModel(model, false, adapter); + ecModel = getECfromGEM(ecModel); + ecModel.metSmiles = repmat({''}, numel(ecModel.mets), 1); + ecModel.metSmiles(strcmp(ecModel.mets,'m1c')) = {'C(C1C)O'}; + + resultFile = [tempname '.csv']; + fid = fopen(resultFile,'w'); + fprintf(fid,'Predicted kcat range: kcat (1/s),Source kcat,Extra Info kcat,Protein Sequence,Substrate\n'); + fprintf(fid,'10 to 100 s^-1,Prediction from CatRange,Predicted range: 10 to 100 s^-1,MRAL,C(C1C)O\n'); + fclose(fid); + cleanupFile = onCleanup(@() delete(resultFile)); + kcatList = readOpenKineticsPredictorOutput(ecModel, 'outFile', resultFile, 'modelAdapter', adapter); + verifyEqual(testCase, kcatList.kcats, 10^1.5, 'RelTol', 1e-12) + verifyEqual(testCase, kcatList.kcatSource, {'OKP-CatRange'}) + + fid = fopen(resultFile,'w'); + fprintf(fid,'Predicted kcat range: kcat (1/s),Source kcat,Extra Info kcat,Protein Sequence,Substrate\n'); + fprintf(fid,'0 to 1e-08 s^-1,Prediction from CatRange,,MRAL,C(C1C)O\n'); + fclose(fid); + kcatList = readOpenKineticsPredictorOutput(ecModel, 'outFile', resultFile, 'modelAdapter', adapter); + verifyEqual(testCase, kcatList.kcats, 5e-9, 'RelTol', 1e-12) +end + function testFetchOpenKineticsPredictorUseStoredMatchesReader_tc0030(testCase) % Verifies fetchOpenKineticsPredictor's 'useStored' path (parsing an % already-downloaded result file, the only path testable without a live