From 9200be77c3529f880564ee9057840d9621c0c592 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Mon, 2 Nov 2026 11:02:38 +0100 Subject: [PATCH] getPfbaEnzymes: fractionOfOptimum for a negative optimum --- src/flux_analysis/getPfbaEnzymes.m | 15 +++++++------ test/unit_tests/geckoCoreFunctionTests.m | 28 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/flux_analysis/getPfbaEnzymes.m b/src/flux_analysis/getPfbaEnzymes.m index 625eddd8..d14bc34e 100644 --- a/src/flux_analysis/getPfbaEnzymes.m +++ b/src/flux_analysis/getPfbaEnzymes.m @@ -21,7 +21,10 @@ % (default: use model.c as given). % fractionOfOptimum : double % fraction of the optimal objective value to fix before minimising -% enzyme usage (default 1.0). +% enzyme usage (default 1.0). A value below 1.0 lets the objective fall +% by (1 - fractionOfOptimum) times the magnitude of its optimum, so it +% also applies to a negative optimum (for example the exchange flux of +% a nutrient that is maximised). % % Returns % ------- @@ -89,12 +92,10 @@ % Fix the objective as a constraint, same "fake metabolite" technique % solveLP's own minFlux=1 option uses (see solveLP.m). -target = fractionOfOptimum * solOrig.f; -if target > 0 - target = target * 0.999999; -else - target = target * 1.000001; -end +% The objective may fall by (1 - fractionOfOptimum*0.999999) of the magnitude +% of its optimum, for a positive and a negative optimum alike (the band that +% setParam(model, 'var', ...) sets around a value). +target = solOrig.f - (1 - fractionOfOptimum*0.999999) * abs(solOrig.f); fixModel = model; fixModel.S(end+1, :) = model.c'; fixModel.mets{end+1, 1} = 'getPfbaEnzymes_objective'; diff --git a/test/unit_tests/geckoCoreFunctionTests.m b/test/unit_tests/geckoCoreFunctionTests.m index 09837df6..e133e721 100644 --- a/test/unit_tests/geckoCoreFunctionTests.m +++ b/test/unit_tests/geckoCoreFunctionTests.m @@ -1264,6 +1264,34 @@ function testPfbaEnzymesMinimisesEnzymeUsage_tc0025(testCase) end +function testPfbaEnzymesFractionOfNegativeOptimum_tc0285(testCase) + % Verifies that 'fractionOfOptimum' below 1 lets a negative optimum fall + % by that fraction of its magnitude. The objective is -R5 with R5 >= 10, + % so its optimum is -10; at 0.9 the objective may fall to -11, and the + % solution stays feasible with R5 at its lower bound. + geckoPath = findGECKOroot; + adapter = ModelAdapterManager.getAdapter(fullfile(geckoPath,'test','unit_tests','ecTestGEM', 'TestGEMAdapter.m')); + model = getGeckoTestModel(); + ecModel = makeEcModel(model, false, adapter); + ecModel = getECfromGEM(ecModel); + ecModel.ec.kcat(:) = 10; + ecModel.ec.source(:) = {'manual'}; + ecModel = applyKcatConstraints(ecModel); + ecModel = setProtPoolSize(ecModel, 0.5, 0.5, 0.5, adapter); + ecModel = setParam(ecModel, 'lb', 'R5', 10); + ecModel = setParam(ecModel, 'obj', 'R5', -1); + + solution = getPfbaEnzymes(ecModel); + verifyEqual(testCase, solution.stat, 1) + verifyEqual(testCase, solution.objectiveValue, -10, 'RelTol', 1e-5) + + solution = getPfbaEnzymes(ecModel, 'fractionOfOptimum', 0.9); + verifyEqual(testCase, solution.stat, 1) + verifyEqual(testCase, solution.objectiveValue, -10, 'RelTol', 1e-5) + verifyGreaterThanOrEqual(testCase, solution.objectiveValue, -11) +end + + function ecModel = fixtureForRelaxProteomicsGreedy() % Builds the shared fixture used by the relaxProteomicsGreedy tests below: % ecTestGEM with uniform kcat=10 and protein pool sized via